Table of Contents

Задача

У меня есть скрипт на Powershell. Мне нужно сделать из него исполняемый файл. Желательно - встроенными средствами Windows (10/2016).

Так как я давно знаю о существовании bat2exe, то задача разбивается на две подзадачи.

Встроить 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.
На что обратить внимание:

  1. Строки, после которых будет продолжение - оканчиваются символом ^. Последняя строка блока powershell - без этого символа!
  2. Между строками - пустые строки. Без них команды powershell склеются в одну длинную строку, что допустимо не всегда.
  3. Экранирование символов. В данном случае экранированы двойные кавычки . Это нужно не всегда, но об этом нужно помнить, если что-то работает не так как нужно.
  4. Не стоит оставлять фигурные скобки в одиночестве на строке.

Вот однострочник 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