PowerShell - Utiliser NSIS (Nullsoft Scriptable Install System) pour simplifier l'usage des scripts PS1

Depuis que je me suis mis à PowerShell, mes premières difficultés avaient été la mise à disposition des scripts, à des tiers (helpdesk, techniciens support ou de proximité), si pendant longtemps, je me suis limité à utiliser PS2EXE, j'ai vite trouvé la limite, il me fallait un moyen plus simple de compiler, corriger ou d'embarquer. C'est à ce moment que je suis tombé sur deux projets; le premier PS1 To EXE Generator de Damien VAN ROBAEYS et Ps1 To Exe de Fatih Kodak pour le second.

Je me suis vite rendu compte que pour le premier, je tombais sur une technologie que je m'étais promis de ne plus utiliser, il y a longtemps WinRar SFX et pour le second, les problèmes de compilations que je voulais éviter.

C'est alors que j'ai pris contact avec Damien VAN ROBAEYS pour comprendre ce qu'il avait fait et échangé nos points de vue, si lui va partir sur une utilisation 7-zip SFX, pour ma part, j'ai réutilisé mon plus vieil allié NSIS (Nullsoft Scriptable Install System) un langage de script d'installation créé par l'équipe derrière Winamp.

⚠ Avant de rentrer dans le vif du sujet, n'utiliser aucune de ces solutions pour cacher un mot de passe, c'est une bêtise à ne pas faire.⚠

Il faudra télécharger et installer votre version de NSIS, depuis son site officiel. Vous pouvez aussi utiliser la version portable proposée par portableapps (qui utilise NSIS pour créer ces applications portables). Dans les deux cas, je vous conseille vivement deux éditeurs Notepad++ et Venis (ce dernier n'est plus mis à jour depuis un petit paquet d'années, cependant, il a un Wizard qui vous aidera à prendre en main et créer vos premiers packages).

Exécuter un script PowerShell déjà présent sur le poste

Voici donc mon premier test, lancer des scripts PowerShell qui se trouve a plusieurs endroits différents du poste, c:\drivers et c:\apps et de récupérer les messages affichés et en récupérer le code retour de chacun.

Vous pouvez remplacer la fonction
nsExec::ExecToStack
par la fonction
ExecWait
pour rendre votre script PowerShell plus ou moins visible.

!include "x64.nsh"

Name "nsExecPS1"

RequestExecutionLevel admin ;Require admin rights

OutFile "ExecPS1.exe"

ShowInstDetails show

Section "Output to variable"
nsExec::ExecToStack 'c:\Drivers\Drivers.bat'
    Pop $0 # return value/error/timeout
    Pop $1 # printed text, up to ${NSIS_MAX_STRLEN}
    DetailPrint '"Install Drivers" printed: $1'
    DetailPrint "       Return value: $0"

    nsExec::ExecToStack 'powershell -ExecutionPolicy bypass -Sta -NoLogo -file "C:\Apps\install.ps1" '
    Pop $0 # return value/error/timeout
    Pop $1 # printed text, up to ${NSIS_MAX_STRLEN}
    DetailPrint '"Install Chocolatey" printed: $1'
    DetailPrint "       Return value: $0"

    nsExec::ExecToStack 'powershell -ExecutionPolicy bypass -Sta -NoLogo -file "C:\Apps\apps.ps1"'
    Pop $0 # return value/error/timeout
    Pop $1 # printed text, up to ${NSIS_MAX_STRLEN}
    DetailPrint '"Install Apps With Chocolatey" printed: $1'
    DetailPrint "       Return value: $0"

    nsExec::ExecToStack 'powershell -ExecutionPolicy bypass -Sta -NoLogo -file "C:\Apps\serial.ps1"'
    Pop $0 # return value/error/timeout
    Pop $1 # printed text, up to ${NSIS_MAX_STRLEN}
    DetailPrint '"Find Serial on bios" printed: $1'
    DetailPrint "       Return value: $0"
SectionEnd

Ce qui donne ceci :

Exécuter un script PowerShell embarqué dans notre package

D'abord, je vais donc créer mon dossier et dedans je vais placer les dossiers, "build" et "src", "build" récupéra l'exécutable du package et "src", aura l'ensemble des sources qui vont avec mon fichier PS1.
À la racine, on va mettre l'icône de notre exécutable et notre script NSI.

Dans ce cas, notre script PS1 possède 4 sections, que l'on traitera avec un paramètre /TYPE= suivi de la section voulu. Dans tous les cas, nous nous servirons des fonctions Delete et RMDir de NSIS pour supprimer les fichiers que l'on ne veut pas qui traine.
Donc une fois que le package sera exécute, il va extraire l'ensemble des données dans le dossier temporaire de l'utilisateur exécutant dans le sous-dossier TEST_NSI, donc ne seront conservé le dossier log.
À la racine, on va mettre l'icône de notre exécutable et notre script NSI.

; ------------------------------------------------------------------------
; Variable
!define APPNAME "Test PS1 et Aborescence"
!define APPEXE "Test_PS1_Tree"
!define VERSION "1.0.0.0"
!define APPVERSION "1.0"
!define APPNAMEANDVERSION "${APPNAME} ${VERSION}"
!define DESCRIPTION "Test ajouter des fichiers avec le Script Powershell et les supprimer"
!define PUBLISHER "Thomas DELACOUR - DELACOURT.OVH"
!define PUBLISHERLIGHT "DELACOURT.OVH"
!define /date YEAR "%Y"

; ------------------------------------------------------------------------
; NSIS includes
!include "x64.nsh"   ; A few simple macros to handle installations on x64 machines
!include "LogicLib.nsh"  ; Logic Lib adds some more familiar flow control and logic to NSI Scripts
;!include "MUI.nsh"   ; Modern UI
!include "nsDialogs.nsh" ; Allows creation of custom pages in the installer
;!include "Memento.nsh"  ; Remember user selections in the installer across runs
!include "FileFunc.nsh" ; Add File Functions
 
; ------------------------------------------------------------------------
; NSIS Settings
AutoCloseWindow true ; Setup close automatically when you finish use
SilentInstall normal ; With Silent we didn't need add /S to have silent installer
RequestExecutionLevel Admin ; Set Uac Level none, user, highest, admin
Icon "Icon.ico" ; Select your Icon file

Unicode true   ; Generate a Unicode installer. It can only be used outside of sections and functions and before any data is compressed. 
SetCompressor /SOLID lzma ; This reduces installer size by approx 30~35% 
; SetCompressor /FINAL lzma ; This reduces installer size by approx 15~18% 

; ------------------------------------------------------------------------ 
; Version Information 
VIAddVersionKey "ProductName" "${APPNAME}"
VIAddVersionKey "CompanyName" "${PUBLISHER}"
VIAddVersionKey "LegalTrademarks" "${APPNAME} is a registered trademark of ${PUBLISHERLIGHT}"
VIAddVersionKey "LegalCopyright" "© ${PUBLISHER}"
VIAddVersionKey "FileDescription" "${DESCRIPTION}"
VIAddVersionKey "FileVersion" "${APPVERSION}"
VIAddVersionKey "ProductVersion" "${APPVERSION}"

VIProductVersion "${VERSION}" ; Format 0.0.0.0 exemple 35.182.2.0 or 1.0.0.1
; ------------------------------------------------------------------------

; ------------------------------------------------------------------------ 
; OutFile 
OutFile ".\build\${APPEXE}_V${APPVERSION}.exe"
; ------------------------------------------------------------------------
Section "Extract"
 SetOverwrite on
 SetOutPath "$Temp\TEST_NSI\"
 File /r "src\"
SectionEnd

Section Parameters
 ${GetParameters} $R0
 ${GetOptions} $R0 "/TYPE=" $R1
 IfErrors 0 all
 Goto error
all:
 StrCmp $R1 "ALL" 0 uni
 DetailPrint "ALL"
 nsExec::ExecToStack '$WINDIR\System32\WindowsPowerShell\v1.0\powershell.exe -sta -noprofile -executionpolicy bypass -file "$Temp\TEST_NSI\TEST.ps1" -ALL'
 Goto delete
uni:
 StrCmp $R1 "UNI" 0 std
 DetailPrint "UNI"
 nsExec::ExecToStack '$WINDIR\System32\WindowsPowerShell\v1.0\powershell.exe -sta -noprofile -executionpolicy bypass -file "$Temp\TEST_NSI\TEST.ps1" -UNINSTALL'
 Goto delete 
std: 
 StrCmp $R1 "STD" 0 error
 DetailPrint "STD"
 nsExec::ExecToStack '$WINDIR\System32\WindowsPowerShell\v1.0\powershell.exe -sta -noprofile -executionpolicy bypass -file "$Temp\TEST_NSI\TEST.ps1" -STD'
 Goto delete
error:
 DetailPrint "Launch GUI"
 nsExec::ExecToStack '$WINDIR\System32\WindowsPowerShell\v1.0\powershell.exe -sta -noprofile -executionpolicy bypass -file "$Temp\TEST_NSI\TEST.ps1" '
 Goto delete

delete:
 Delete "$Temp\TEST_NSI\*.*"
 Delete "$Temp\TEST_NSI\resources\*.*"
 Delete "$Temp\TEST_NSI\assembly\*.*"
 Delete "$Temp\TEST_NSI\test\*.*"
 RMDir /r "$Temp\TEST_NSI\resources"
 RMDir /r "$Temp\TEST_NSI\assembly"
 RMDir /r "$Temp\TEST_NSI\test"

SectionEnd

BrandingText "${PUBLISHERLIGHT} ${YEAR}"


Exécuter un script PowerShell qui possède des paramètres à configurer

Dans cet exemple, je vais générer une page intermédiaire dans NSIS qui va permettre de remplir 4 paramètres dans mon script PowerShell

D'abord, je vais donc créer mon dossier et dedans je vais placer les dossiers, "build" et "src", "build" récupéra l'exécutable du package et src, aura l'ensemble des sources qui vont avec mon fichier PS1.
À la racine, on va mettre l'icône de notre exécutable et notre script NSI.

Le package appellera silencieusement le script This_is_a_test.ps1 qui possède 4 paramètre comme ceci : 

[CmdletBinding()] 
    Param 
    ( 
        [Parameter(Mandatory=$False,Position=0)][String]$EventPort,
        [Parameter(Mandatory=$False,Position=0)][String]$DataPort,
        [Parameter(Mandatory=$False,Position=0)][String]$MaxDays,
        [Parameter(Mandatory=$False,Position=0)][String]$MaxCount
    )
Le package a la capacité d'être silencieux, car NSIS a déjà le /S (⚠attention à la casse) pour lancer un NSIS silencieusement, et donc si l'on veut récupérer les paramètres alors, j'utilise la fonction GetParameters pour compléter les variables dans NSIS qui l'est utilisera dans la ligne de commande powershell


Section Parameters
 ${GetParameters} $R0
 ${GetOptions} $R0 "/Param1=" $EventPort
 ${GetOptions} $R0 "/Param2=" $DataPort
 ${GetOptions} $R0 "/Param3=" $MaxDays
 ${GetOptions} $R0 "/Param4=" $MaxCount
SectionEnd
Pour la partie dialogue j'ai utilisé ceci : NSIS Dialog Designer qui permettra de vous aider à créer un page dialog avec des textes, des zones à taper ou encore des boutons,radio et liste. Je vais pas rentrer dans les détails ici.

Voici donc le résultat :
Et le code source:
; ------------------------------------------------------------------------
; Variable
!define APPNAME "This is a test"
!define APPEXE "This_is_a_test"
!define VERSION "1.0.1.0"
!define APPVERSION "1.0"
!define APPNAMEANDVERSION "${APPNAME} ${VERSION}"
!define DESCRIPTION "Ici nous allons embarquer un ou plusieurs fichiers"
!define PUBLISHER "Thomas DELACOUR - DELACOURT.OVH"
!define PUBLISHERLIGHT "DELACOURT.OVH"
!define /date YEAR "%Y"

; ------------------------------------------------------------------------
; NSIS includes
!include "x64.nsh"   ; A few simple macros to handle installations on x64 machines
!include "LogicLib.nsh"  ; Logic Lib adds some more familiar flow control and logic to NSI Scripts
;!include "MUI.nsh"   ; Modern UI
!include "nsDialogs.nsh" ; Allows creation of custom pages in the installer
;!include "Memento.nsh"  ; Remember user selections in the installer across runs
!include "FileFunc.nsh" ; Add File Functions
; ------------------------------------------------------------------------
; NSIS Settings
;AutoCloseWindow true ; Setup close automatically when you finish use
;SilentInstall silent ; With Silent we didn't need add /S to have silent installer
RequestExecutionLevel User ; Set Uac Level none, user, highest, admin
XPStyle on
Icon "Icon.ico" ; Select your Icon file
Unicode true   ; Generate a Unicode installer. It can only be used outside of sections and functions and before any data is compressed.
SetCompressor /SOLID lzma ; This reduces installer size by approx 30~35%
; SetCompressor /FINAL lzma ; This reduces installer size by approx 15~18%

; ------------------------------------------------------------------------
; Version Information
VIAddVersionKey "ProductName" "${APPNAME}"
VIAddVersionKey "CompanyName" "${PUBLISHER}"
VIAddVersionKey "LegalTrademarks" "${APPNAME} is a registered trademark of ${PUBLISHERLIGHT}"
VIAddVersionKey "LegalCopyright" "© ${PUBLISHER}"
VIAddVersionKey "FileDescription" "${DESCRIPTION}"
VIAddVersionKey "FileVersion" "${APPVERSION}"
VIAddVersionKey "ProductVersion" "${APPVERSION}"

VIProductVersion "${VERSION}" ; Format 0.0.0.0 exemple 35.182.2.0 or 1.0.0.1
; ------------------------------------------------------------------------

; Main Install settings
; Name of package
Name "${APPNAMEANDVERSION}"
; Installation directory
InstallDir "$Temp\Thisisatest\"
; ------------------------------------------------------------------------
; OutFile
OutFile ".\Build\${APPEXE}_V${APPVERSION}.exe"
; ------------------------------------------------------------------------

; Add custom page
Page custom nsDialogsPage nsDialogsPageLeave
Page instfiles
; ------------------------------------------------------------------------

; Check on Initialisation if we have dotnet 3.5 on this computer.
Function .onInit

GetDllVersion "$sysdir\mscoree.dll" $R0 $R1

IntOp $R0 $R0 / 0x00010000

${If} $R0 >= 2

 !define RUNTIME_INFO_UPGRADE_VERSION 0x01

 !define RUNTIME_INFO_DONT_RETURN_DIRECTORY 0x10

 !define RUNTIME_INFO_DONT_SHOW_ERROR_DIALOG 0x40

 System::Call 'mscoree::GetRequestedRuntimeInfo(i0,i0,i0,i0,i ${RUNTIME_INFO_UPGRADE_VERSION}|${RUNTIME_INFO_DONT_RETURN_DIRECTORY}|${RUNTIME_INFO_DONT_SHOW_ERROR_DIALOG},\i0,i0,*i,w .r0, i ${NSIS_MAX_STRLEN}, *i)i.r1'

 ${If} $1 == 0

  SetErrorLevel 1
  DetailPrint "OK, the Dotnet framework 3.5 is installed"

 ${Else}

  MessageBox mb_ok error=$1
  SetErrorLevel 4
  DetailPrint "Error, the Dotnet framework 3.5 isn't installed"
  Quit

 ${EndIf}

${EndIf}

FunctionEnd
;------------------------------------------------------------------------

; handle variables
Var Service
Var Service_ServiceConfigGroupBox
Var Service_Info
Var Service_EventPortLabel
Var Service_DataPortLabel
Var Service_MaxCountLabel
Var Service_MaxDaysLabel
Var Service_EventPort
Var Service_DataPort
Var Service_MaxCount
Var Service_MaxDays
Var EventPort
Var DataPort
Var MaxDays
Var MaxCount
; ------------------------------------------------------------------------

; dialog create function
Function fnc_This_is_a_test

  ; === This_is_a_test (type: Dialog) ===
  nsDialogs::Create 1018
  Pop $Service
  ${If} $Service == error
    Abort
  ${EndIf}

  ; === ServiceConfigGroupBox (type: GroupBox) ===
  ${NSD_CreateGroupBox} 0u 0u 100% 100% "Service Configuration"
  Pop $Service_ServiceConfigGroupBox

  ; === Info (type: Label) ===
  ${NSD_CreateLabel} 10u 20u 95% 20u "Here you can configure the settings of ..."
  Pop $Service_Info

  ; === EventPortLabel (type: Label) ===
  ${NSD_CreateLabel} 10u 48u 43u 12u "Event Port:"
  Pop $Service_EventPortLabel

  ; === DataPortLabel (type: Label) ===
  ${NSD_CreateLabel} 10u 64u 43u 12u "Data Port:"
  Pop $Service_DataPortLabel

  ; === MaxDaysLabel (type: Label) ===
  ${NSD_CreateLabel} 10u 80u 43u 12u "Max Days:"
  Pop $Service_MaxDaysLabel

  ; === MaxCountLabel (type: Label) ===
  ${NSD_CreateLabel} 10u 96u 43u 12u "Max Count:"
  Pop $Service_MaxCountLabel

  ; === EventPort (type: Text) ===
  ${NSD_CreateText} 65u 46u 66u 12u "0"
  Pop $Service_EventPort

  ; === DataPort (type: Text) ===
  ${NSD_CreateText} 65u 62u 66u 12u "0"
  Pop $Service_DataPort

  ; === MaxDays (type: Text) ===
  ${NSD_CreateText} 65u 78u 66u 12u "0"
  Pop $Service_MaxDays

  ; === MaxCount (type: Text) ===
  ${NSD_CreateText} 65u 94u 66u 12u "0"
  Pop $Service_MaxCount

FunctionEnd

; dialog show function
Function nsDialogsPage
 Call fnc_This_is_a_test
 nsDialogs::Show
FunctionEnd

; Set variable from text add on custom page
Function nsDialogsPageLeave
 ${NSD_GetText} $Service_EventPort $4
 ${NSD_GetText} $Service_DataPort $5
 ${NSD_GetText} $Service_MaxDays $6
 ${NSD_GetText} $Service_MaxCount $7
FunctionEnd

Section "DisableX64FSRedirection"

 ${If} ${RunningX64}
    ${DisableX64FSRedirection}
    DetailPrint "Disabling Windows 64-bit file system redirection"
 ${EndIf}

SectionEnd

Section Parameters
 ${GetParameters} $R0
 ${GetOptions} $R0 "/Param1=" $EventPort
 ${GetOptions} $R0 "/Param2=" $DataPort
 ${GetOptions} $R0 "/Param3=" $MaxDays
 ${GetOptions} $R0 "/Param4=" $MaxCount
SectionEnd

Section Set
  IfSilent SILENTMODE SILENTOFF
  SILENTMODE:
  StrCpy $4 "Param1"
  StrCpy $5 "Param2"
  StrCpy $6 "Param3"
  StrCpy $7 "Param4"
  SILENTOFF:

 ${If} $EventPort == ""

  StrCpy $R1 $4

    ${Else}

  StrCpy $R1 $EventPort
  
    ${EndIf} 

 ${If} $DataPort == ""

  StrCpy $R2 $5

    ${Else}

  StrCpy $R2 $DataPort

    ${EndIf} 
 
 ${If} $MaxDays == ""

  StrCpy $R3 $6
  
    ${Else}

  StrCpy $R3 $MaxDays

    ${EndIf} 
 
 ${If} $MaxCount == ""

  StrCpy $R4 $7
    ${Else}
 
  StrCpy $R4 $MaxCount

    ${EndIf}

DetailPrint "Action 1: $R1"
DetailPrint "Action 2: $R2"
DetailPrint "Action 3: $R3"
DetailPrint "Action 4: $R4"

SectionEnd

Section Install

SetOverwrite on

SetOutPath "$INSTDIR"
 File /r "src\"
  nsExec::ExecToStack '$WINDIR\System32\WindowsPowerShell\v1.0\powershell.exe -sta -noprofile -executionpolicy bypass -file "$INSTDIR\This_is_a_test.ps1" -EventPort $R1 -DataPort $R2 -MaxDays $R3 -MaxCount $R4'
  Delete "$INSTDIR\This_is_a_test.ps1"

SectionEnd

Section "EnableX64FSRedirection"

 ${If} ${RunningX64}
    ${EnableX64FSRedirection}
    DetailPrint "Re-enabling Windows 64-bit file system redirection"
 ${EndIf}

SectionEnd

BrandingText "${PUBLISHERLIGHT} ${YEAR}"

Commentaires

Posts les plus consultés de ce blog

Powershell - Supprimer Teams sur l'ensemble des profils utilisateurs

Powershell - Comment tester les ports TCP ?

MRemoteNG - Voir les mots de passe dans l'application