Задача
У меня есть скрипт на Powershell. Мне нужно сделать из него исполняемый файл. Желательно - встроенными средствами Windows (10/2016).
Так как я давно знаю о существовании bat2exe, то задача разбивается на две подзадачи.
- встроить многострочный скрипт Powershell в bat-скрипт
- конвертировать полученный bat-скрипт в исполняемый exe-файл
Встроить PowerShell-скрипт в bat-скрипт
https://stackoverflow.com/questions/9366080/batch-launching-powershell-with-a-multiline-command-parameter
Оказалось все несложно при соблюдении некоторых условностей.
Для исполнения многострочной команды powershell внутри bat-скрипта нужно делать так:
powershell -command ^ for ($i=1; $i -lt 10; $i++){^ Write-Host $(\"Iteration - $i\") }^ Write-Host
Это bat-скрипт, исполняющий блок кода Powershell.
На что обратить внимание:
- Строки, после которых будет продолжение - оканчиваются символом ^. Последняя строка блока powershell - без этого символа!
- Между строками - пустые строки. Без них команды powershell склеются в одну длинную строку, что допустимо не всегда.
- Экранирование символов. В данном случае экранированы двойные кавычки “. Это нужно не всегда, но об этом нужно помнить, если что-то работает не так как нужно.
- Не стоит оставлять фигурные скобки в одиночестве на строке.
Вот однострочник bash, который берет заданный Powershell-скрипт и выводит соответствующий ему bat-скрипт:
echo -e "powershell -command ^\n\n`sed 's/"/\\\\"/g' ./Script.ps1 | sed '/^#.*$/d' | sed '/^[[:space:]]*$/d' | sed 's/$/\^\r\n/g'`\n\n#"
Вот более сложный скрипт, который запускает IDE Eclipse, установленную в WSL (Windows Subsystem For Linux):
powershell -command ^ $UbuntuAppxPackagename='CanonicalGroupLimited.Ubuntu18.04onWindows'^ $ubuntu_exe='ubuntu1804.exe'^ $ubuntu_exe=\"$($(Get-AppxPackage | Where {$_.Name -eq $UbuntuAppxPackagename }).InstallLocation)\$ubuntu_exe\"^ $username=$(whoami /upn).Split('@')[0]^ $XServerProcessName='vcxsrv'^ $XServerExePath='C:\Program Files\VcXsrv\vcxsrv.exe'^ $XServerArguments=':0 -multiwindow -clipboard -wgl'^ $cmd = '\"' + $ubuntu_exe + '\" ' + \"run useradd -m -s /bin/bash $username\"^ cmd /c $cmd^ if ($(Get-Process -Name $XServerProcessName) -eq $null) { Start-Process -NoNewWindow -FilePath $XServerExePath -ArgumentList $XServerArguments }^ $cmd = '\"' + $ubuntu_exe + '\" ' + \"run sudo -u $username DISPLAY=:0.0 /opt/eclipse/eclipse\"^ cmd /c $cmd
Конвертирование bat в exe с помощью встронных средств Windows
https://stackoverflow.com/questions/51098378/converting-bat-to-exe-with-no-additional-external-software-create-sfx/51104332
https://github.com/npocmaka/batch.scripts/edit/master/hybrids/iexpress/bat2exeIEXP.bat
Это вообще похоже на магию.
Есть такая утилита IEXPRESS. Она есть во всех версиях Windows, начиная с Windows 2000 и предназначена для создания самораспаковывающихся архивов из набора файлов.
Вот bat-файл, который делает то что нужно.
Пользоваться им очень просто - создаем файл с этим скриптом и перетаскиваем на его иконку bat-скрипт, который нужно сконвертировать в exe. Всё. Рядом с исходным скриптом появится exe-файл.
;@echo off ;Title Converting batch scripts to file.exe with iexpress ;Mode 75,3 & color 0A ;Rem Original Script https://github.com/npocmaka/batch.scripts/edit/master/hybrids/iexpress/bat2exeIEXP.bat ;echo( ;if "%~1" equ "" ( ;echo Usage : Drag and Drop your batch file over this script:"%~nx0" ;Timeout /T 5 /nobreak>nul & Exit ;) ;set "target.exe=%__cd__%%~n1.exe" ;set "batch_file=%~f1" ;set "bat_name=%~nx1" ;set "bat_dir=%~dp1" ;Set "sed=%temp%\2exe.sed" ;echo Please wait a while ... Creating "%~n1.exe" ... ;copy /y "%~f0" "%sed%" >nul ;( ;(echo() ;(echo(AppLaunched=cmd /c "%bat_name%") ;(echo(TargetName=%target.exe%) ;(echo(FILE0="%bat_name%") ;(echo([SourceFiles]) ;(echo(SourceFiles0=%bat_dir%) ;(echo([SourceFiles0]) ;(echo(%%FILE0%%=) ;)>>"%sed%" ;iexpress /n /q /m %sed% ;del /q /f "%sed%" ;exit /b 0 [Version] Class=IEXPRESS SEDVersion=3 [Options] PackagePurpose=InstallApp ShowInstallProgramWindow=0 HideExtractAnimation=1 UseLongFileName=1 InsideCompressed=0 CAB_FixedSize=0 CAB_ResvCodeSigning=0 RebootMode=N InstallPrompt=%InstallPrompt% DisplayLicense=%DisplayLicense% FinishMessage=%FinishMessage% TargetName=%TargetName% FriendlyName=%FriendlyName% AppLaunched=%AppLaunched% PostInstallCmd=%PostInstallCmd% AdminQuietInstCmd=%AdminQuietInstCmd% UserQuietInstCmd=%UserQuietInstCmd% SourceFiles=SourceFiles [Strings] InstallPrompt= DisplayLicense= FinishMessage= FriendlyName=- PostInstallCmd=<None> AdminQuietInstCmd=
Единственным недостатком этого метода можно считать открытое окно консоли на фоне.
Скрываем окно любого процесса средствами Powershell
https://community.idera.com/database-tools/powershell/powertips/b/tips/posts/show-or-hide-windows
Нижеприведенный powershell-код позволяет скрыть окно любого процесса:
Enum ShowStates { Hide = 0 Normal = 1 Minimized = 2 Maximized = 3 ShowNoActivateRecentPosition = 4 Show = 5 MinimizeActivateNext = 6 MinimizeNoActivate = 7 ShowNoActivate = 8 Restore = 9 ShowDefault = 10 ForceMinimize = 11 } # the C#-style signature of an API function (see also www.pinvoke.net) $code = '[DllImport("user32.dll")] public static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow);' # add signature as new type to PowerShell (for this session) $type = Add-Type -MemberDefinition $code -Name myAPI -PassThru # access a process # (in this example, we are accessing the current PowerShell host # with its process ID being present in $pid, but you can use # any process ID instead) $process = Get-Process -Id $PID # get the process window handle $hwnd = $process.MainWindowHandle # apply a new window size to the handle, i.e. hide the window completely $type::ShowWindowAsync($hwnd, [ShowStates]::Hide) Start-Sleep -Seconds 2 # restore the window handle again $type::ShowWindowAsync($hwnd, [ShowStates]::Show)
Чтобы работало в старых версиях Powershell (2-4) определение типов видимости нужно делать так:
Add-Type -TypeDefinition ' public enum ShowStates { Hide = 0, Normal = 1, Minimized = 2, Maximized = 3, ShowNoActivateRecentPosition = 4, Show = 5, MinimizeActivateNext = 6, MinimizeNoActivate = 7, ShowNoActivate = 8, Restore = 9, ShowDefault = 10, ForceMinimize = 11, } '
Вот bat-скрипт, который скрывает окно консоли и делает нужную работу (открывает процесс средствами powershell):
powershell -command ^ Enum ShowStates {^ Hide = 0^ Normal = 1^ Minimized = 2^ Maximized = 3^ ShowNoActivateRecentPosition = 4^ Show = 5^ MinimizeActivateNext = 6^ MinimizeNoActivate = 7^ ShowNoActivate = 8^ Restore = 9^ ShowDefault = 10^ ForceMinimize = 11 } ^ $code = '[DllImport(\"user32.dll\")] public static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow);'^ $type = Add-Type -MemberDefinition $code -Name myAPI -PassThru ^ $ParentProcessId = $(Get-CimInstance Win32_Process -Filter \"ProcessId = $PID\").ParentProcessId^ $process = Get-Process -Id $ParentProcessId^ $hwnd = $process.MainWindowHandle^ $type::ShowWindowAsync($hwnd, [ShowStates]::Hide)^ #############################################################^ $UbuntuAppxPackagename=\"CanonicalGroupLimited.Ubuntu18.04onWindows\"^ $ubuntu_exe=\"$($(Get-AppxPackage | Where {$_.Name -eq $UbuntuAppxPackagename }).InstallLocation)\ubuntu1804.exe\"^ $username=$(whoami /upn).Split('@')[0]^ $XServerProcessName='vcxsrv'^ $XServerExePath='C:\Program Files\VcXsrv\vcxsrv.exe'^ $XServerArguments=':0 -multiwindow -clipboard -wgl'^ $cmd = '\"' + $ubuntu_exe + '\" ' + \"run useradd -m -s /bin/bash $username\"^ cmd /c $cmd^ if ($(Get-Process -Name $XServerProcessName) -eq $null) { Start-Process -NoNewWindow -FilePath $XServerExePath -ArgumentList $XServerArguments }^ $cmd = '\"' + $ubuntu_exe + '\" ' + \"run sudo -u $username DISPLAY=:0.0 /opt/eclipse/eclipse\"^ cmd /c $cmd
Discussion