У меня есть скрипт на Powershell. Мне нужно сделать из него исполняемый файл. Желательно - встроенными средствами Windows (10/2016).
Так как я давно знаю о существовании bat2exe, то задача разбивается на две подзадачи.
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.
На что обратить внимание:
Вот однострочник 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
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=
Единственным недостатком этого метода можно считать открытое окно консоли на фоне.
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