Skip to content

Instantly share code, notes, and snippets.

@scriptingstudio
Last active July 9, 2026 08:06
Show Gist options
  • Select an option

  • Save scriptingstudio/2e7a6c1aa61406ca7dd24b47cb8eb1f7 to your computer and use it in GitHub Desktop.

Select an option

Save scriptingstudio/2e7a6c1aa61406ca7dd24b47cb8eb1f7 to your computer and use it in GitHub Desktop.
Version 2. PS2EXE script deeply refactored replacing outdated techniques and overloaded logics with modern look and feel. Source compressor added to reduce output size. Source encoding, the return of the king. Implemented as a dotsource module.
#Requires -Version 5
# Insipred by and credits to PS2EXE by Markus Scholts (https://github.com/MScholtes/PS2EXE)
# Deeply refactored original code replacing outdated techniques and overloaded logics with modern look and feel. Full backward compatibility provided.
# Source compressor added to significantly reduce output size. Source encoding, the return of the king. Experimental support for PowerShell Core introduced.
<# TODO:
- remove "extract" facility (from C#)
#>
<#
.SYNOPSIS
Converts powershell scripts to standalone executables.
.DESCRIPTION
Converts powershell scripts to standalone executables. GUI output and input is activated with one switch, real windows executables are generated. You may use the graphical counterpart Ps2exedotNet for convenience.
Please see Remarks on original project page for topics "GUI mode output formatting", "Config files", "Password security", "Script variables" and "Window in background in -noConsole mode".
A generated executable has the following service parameters:
-? [<MODIFIER>] PowerShell help text of the script inside the executable. The optional parameter combination "-? -detailed", "-? -examples", or "-? -full" can be used to get the appropriate help text.
-debug Forces the executable to be debugged. It calls "System.Diagnostics.Debugger.Launch()" method.
-extract:<FILENAME> Extracts the PowerShell script inside the executable and saves it as FileName.
The script will not be executed.
-wait At the end of the script execution it writes "Hit any key to exit..." and waits for a key to be pressed.
-end All following options will be passed to the script inside the executable.
All preceding options are used by the executable itself.
.PARAMETER InputFile
Specifies a PowerShell script to convert to executable (file has to be UTF8 or UTF16 encoded).
.PARAMETER ScriptBlock
Specifies a scriptblock to convert to executable.
.PARAMETER OutputFile
Destination executable file name or folder, defaults to inputFile with extension '.exe'.
.PARAMETER Platform
Specifies a target platform for the compiled executable. Valid values are x64, x86, any.
.PARAMETER Lcid
Specifies a locale ID for the compiled executable. Current user culture if not specified.
.PARAMETER MTA
Indicates to use Multi Thread Apartment mode, otherwise - STA.
.PARAMETER NoConsole
Indicates that the resulting executable will be a Windows Forms app without a console window.
You might want to pipe your output to Out-String to prevent a message box for every line of output
(example: dir C:\ | Out-String).
.PARAMETER ConHost
Indicates to force start with conhost as console instead of Windows Terminal. If necessary a new console window will appear.
Important: Disables redirection of input, output or error channel!
.PARAMETER Unicode
Specifies encode output as Unicode in console mode, useful to display special encoded chars.
.PARAMETER CredentialGUI
Indicates to use GUI for prompting credentials in console mode instead of console input.
.PARAMETER IconFile
Specifies icon file name for the compiled executable. To set default enter "system", otherwise the icon from powershell.exe will be used
.PARAMETER EmbedFiles
Specifies paths to files to embed given as hashtable, will be extracted at runtime to the keys of the hashes, source file names must be unique, e.g. -embedFiles @{'Targetfilepath'='Sourcefilepath'}.
Absolute and relative paths are allowed. For target paths a relative path beginning with '.\' is interpreted as relative to the executable, without the leading '.\' as relative to the current path at runtime.
Directories are created automaticly on startup if necessary.
In the target path environment variables in cmd.exe notation like %TEMP% or %APPDATA% are expanded at runtime.
.PARAMETER Title
Specifies title information (displayed in details tab of Windows Explorer's properties dialog).
.PARAMETER Description
Specifies description information (not displayed, but embedded in executable).
.PARAMETER Company
Specifies company information (not displayed, but embedded in executable).
.PARAMETER Product
Specifies product information (displayed in details tab of Windows Explorer's properties dialog).
.PARAMETER Copyright
Specifies copyright information (displayed in details tab of Windows Explorer's properties dialog).
.PARAMETER Trademark
Specifies trademark information (displayed in details tab of Windows Explorer's properties dialog).
.PARAMETER Version
Specifies version information (displayed in details tab of Windows Explorer's properties dialog).
.PARAMETER ConfigFile
Indicates to write a config file (<outputfile>.exe.config).
.PARAMETER PrepareDebug
Indicates to create helpful information for debugging of generated executable. See parameter -debug there.
.PARAMETER NoOutput
Indicates that the resulting executable will generate no standard output (includes verbose and information channel).
.PARAMETER NoError
Indicates that the resulting executable will generate no error output (includes warning and debug channel).
.PARAMETER NoVisualStyles
Indicates to disable visual styles for a generated windows GUI application. Only applicable with parameter -noConsole.
.PARAMETER ExitOnCancel
Indicates to exit program when Cancel or "✕" is selected in a Read-Host input box. Only applies with parameter -noConsole.
.PARAMETER DPIAware
Indicates that if display scaling is enabled, GUI controls will be scaled if possible.
.PARAMETER WinFormsDPIAware
Indicates to create an entry in the config file for WinForms to use DPI scaling. Forces -configFile and -supportOS.
.PARAMETER RequireAdmin
Indicates that if UAC is enabled, compiled executable will run only in elevated context (UAC dialog appears if required).
.PARAMETER SupportOS
Indicates to use features of latest Windows versions (run [Environment]::OSVersion to see the difference).
.PARAMETER Virtualize
Indicates that application virtualization is activated (forcing x86 runtime).
.PARAMETER LongPaths
Indicates to enable long paths (> 260 characters) if enabled on OS (works only with Windows 10 or up).
.PARAMETER NoCompress
Indicates to not use a PS compression for the source.
.PARAMETER Basic
Indicates to use a light compression mode. Omits comments and indented formatting in the output string. Ignored when -noCompress parameter is specified.
.PARAMETER Core
Experimental. Indicates to compile executable for .NET to support PowerShell Core.
Deployment strategy for output file: single executable file
Ways to accomplish:
- Framework-dependent Deployment: PublishSingleFile (default)
- Self-contained Deployment + Framework-independent: PublishSingleFile + PublishSelfContained [+ PublishTrimmed]
.PARAMETER TargetOS
Option for -Core. Specifies target operating system. Valid values are w[indows], l[inux], m[acos].
.PARAMETER Arm
Option for -Core. Indicates to compile executable for ARM platforms.
.PARAMETER DotnetVersion
Option for -Core. Specifies .NET version for target system.
.PARAMETER Solo
Option for -Core. Enables 'SelfContained' mode.
.PARAMETER NoCache
Option for -Core. Indicates to omit checking for changes of build parameters.
.PARAMETER RtProfile
Experimental.
.PARAMETER Run
Indicates to invoke output file immediately after creation.
.EXAMPLE
Invoke-ps2exe C:\Data\MyScript.ps1
Converts C:\Data\MyScript.ps1 to C:\Data\MyScript.exe as console executable
.EXAMPLE
ps2exec -inputFile C:\Data\MyScript.ps1 -outputFile C:\Data\MyScriptGUI.exe -iconFile C:\Data\Icon.ico -noConsole -title "MyScript" -version 1.0
Converts C:\Data\MyScript.ps1 to C:\Data\MyScriptGUI.exe as graphical executable, icon and meta data
.NOTES
Version: 0.5.0.33
Date: 2025-08-19
Author: Ingo Karstein, Markus Scholtes
Initial refactor release: 2026-03-08
Input source encoding caveat: hackers and bad guys can employ this tool to hide malware in EXE files and then Microsoft can block the script via Defender.
.LINK
https://github.com/MScholtes/PS2EXE
https://gist.github.com/scriptingstudio/2e7a6c1aa61406ca7dd24b47cb8eb1f7
#>
function Invoke-Ps2exe {
[CmdletBinding(DefaultParameterSetName = 'File')]
[Alias('ConvertTo-Exe','cvpe','ipe','ps2exe','p2e')]
param (
# Input objects
[Parameter(Position = 0, ParameterSetName = 'File')]
[string] $inputFile,
[Parameter(Position = 0, ParameterSetName = 'ScriptBlock')]
[scriptblock] $scriptBlock,
[string] $outputFile,
[hashtable] $embedFiles,
# Application metadata
[string] $iconFile,
[string] $title = 'PowerShell',
[string] $description, [string] $company, [string] $product,
[string] $copyright, [string] $trademark,
[string] $version, # [string] because of buggy [version] not accepting a single decimal
# Application runtime options
[alias('profile')][string[]] $rtprofile, # experimental
[ValidateSet('x86','x64','Anycpu')]
[string] $platform = 'x64',
[switch] $noConsole, [switch] $conHost,
[switch] $noOutput, [switch] $noError,
[alias('locale')][int] $lcid,
[switch] $MTA,
[alias('UNICODEEncoding')][switch] $unicode,
[switch] $configFile,
[alias('admin')][switch] $requireAdmin,
[switch] $exitOnCancel,
[switch] $credentialGUI,
[switch] $supportOS, # confusing
[switch] $longPaths, # confusing
[switch] $virtualize,
[switch] $prepareDebug, [switch] $raw, # prepareDebug's option
[switch] $noCompress, [switch] $basic, # use basic compression
[switch] $run,
#[alias('silent')][switch] $quiet, # experimental
# PowerShell Core options; experimental
[switch] $core, # mode activator
[string] $targetOS, # w[indows], l[inux], m[acos]; option for $core
[switch] $arm, # option for $core
[alias('dnv')][string] $dotnetversion, # option for $core
[alias('aio','sc','selfcontained')][switch] $solo, # self-contained
[switch] $nocache, # build option; option for $core
# useless rubbish
[switch] $noVisualStyles, # obsolete
[switch] $DPIAware, # confusing
# Legacy/obsolete options (remains for backward compatibility)
[Parameter(DontShow)] [switch] $x86,
[Parameter(DontShow)] [switch] $x64,
[Parameter(DontShow)] [switch] $STA,
[Parameter(DontShow)] [switch] $noConfigFile,
# Internal use (service)
[Parameter(DontShow)]
[switch] $winFormsDPIAware, # confusing
[Parameter(DontShow)]
[alias('nested')][switch] $shellmode # don't use it manually!
)
if (-not $shellmode) {
Write-Host "PowerShell script converter to standalone executable v2.0`n"
}
if ($isMacos -or $islinux) { # experimental
Write-Warning 'Operation is not supported on this platform. Windows only.'
return
}
if (-not $inputFile -and -not $scriptblock) {
Show-ModuleHelp
return
}
# PS host router for PS.Core mode
Resolve-PSHost $PSBoundParameters -shellmode:$shellmode
# Resolve target paths
if ($inputFile) {
$inputFile = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($inputFile) # good staff
if (-not (Test-Path -LiteralPath $inputFile -PathType Leaf)) {
Write-Error "Input file '$inputfile' not found!"
return
}
if ($inputFile -match 'RevShell|Update-KB4524147|Rek4m2ell|UpdatxK1q24147') {
Write-Error "PS2EXEC did not convert this because PS2EXEC does not like malware." -Category ParserError -ErrorId RuntimeException # legacy bullshit
return
}
$outputFile = if (-not $outputFile) {
[System.IO.Path]::ChangeExtension($inputFile,'exe')
}
else {
$fn = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($outputFile)
if (Test-Path -Literalpath $fn -PathType Container) {
Join-Path $outputFile ([System.IO.Path]::GetFileNameWithoutExtension($inputFile) + '.exe')
}
else {$fn}
}
}
else { # scriptblock
if (-not $outputFile) {Write-Warning 'Output file not specified. A random name will be created.'}
$fn = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($outputFile)
if (Test-Path -Literalpath $fn -PathType Container) {
$fn = [System.IO.Path]::GetFileNameWithoutExtension([System.IO.Path]::GetRandomFileName())
$outputFile = Join-Path $PWD.Path "$fn.exe"
}
}
$workingDirectory = [System.IO.Path]::GetDirectoryName($outputFile)
if ($outputFile -notmatch '\.exe$') {
Write-Error 'Output file must have ".exe" extension!'
return
}
if ($inputFile -and ($inputFile -eq $outputFile)) {
Write-Error 'Input and output filenames cannot be the same!'
return
}
if ($iconFile -match '^(%|system)$') {$iconFile = ''} # system icon
elseif ($iconFile) {
$iconFile = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($iconFile)
if (-not (Test-Path -LiteralPath $iconFile -PathType Leaf)) {
Write-Error "Icon file '$iconFile' not found!"
return
}
} else { # default icon; [System.Drawing.Icon]
$iconFile = Get-DefaultIcon
}
# end Resolve target paths
#region Resolve parameterset collisions/dependencies
Set-ParamProfile # experimental
if ($winFormsDPIAware) {
$supportOS = $true
}
if ($noConsole -and $conHost) {
Write-Error "-noConsole cannot be used with -conHost."
return
}
if ($requireAdmin -and $virtualize) {
Write-Error "-requireAdmin cannot be used with -virtualize."
return
}
if ($supportOS -and $virtualize) {
Write-Error "-supportOS cannot be used with -virtualize."
return
}
if ($longPaths -and $virtualize) {
Write-Error "-longPaths cannot be used with -virtualize."
return
}
if ($noConfigFile -and $configFile) {
Write-Error "-configFile cannot be used with -noConfigFile."
return
}
if (-not $configFile -and $longPaths) {
Write-Warning "Forcing generation of a config file since the option -longPaths required."
$configFile = $true
}
if (-not $configFile -and $winFormsDPIAware) {
Write-Warning "Forcing generation of a config file since the option -winFormsDPIAware required."
$configFile = $true
}
if ($noConsole) {$MTA = $false}
$STA = -not $MTA # STA flag applies in C#
# normalize metadata
$Title = $Title -replace '\\', '\\' # WTF?
$Product = $Product -replace '\\', '\\'
$Copyright = $Copyright -replace '\\', '\\'
$Trademark = $Trademark -replace '\\', '\\'
$Description = $Description -replace '\\', '\\'
$Company = $Company -replace '\\', '\\'
# normalize version
if (-not [string]::IsNullOrEmpty($version)) {$version = $version.trim()}
if ($version) {
if ($version -notmatch '\.') {$version = "$version.0"} # fix dotnet flaw
if ($version -match '\.$') {$version = "${version}0"} # fix dotnet flaw
if ($version -match '\.\.') {$version = $version.replace('..','.0.').replace('..','.0.')}
if (-not ($version -as [version])) {
Write-Error "Invalid version specified. Valid version format is n.n.n.n, n.n.n, n.n, or n ('n' is numeric val)!"
return
}
} else {
Write-Warning 'Version not specified. Defaults to 1.0.0.0'
$version = '1.0.0.0'
}
#endregion parameter resolving
if ($core) {
Invoke-DotnetCompiler
} else {
Invoke-DomCompiler
}
if (Test-Path "$env:TEMP\ps2exedefault.ico") {Remove-Item "$env:TEMP\ps2exedefault.ico" -Force}
} # END Invoke-Ps2exe
function Set-ParamProfile { # experimental
if ($null -eq $script:rtprofile) {return}
switch ($script:rtprofile) {
'gui' {
$script:noconsole = $true
$script:nooutput = $true
$script:noerror = $true
}
'console' {
$script:mta = $true
$script:unicode = $true
$script:supportOS = $true
$script:longpaths = $true
}
'winforms' {
$script:credentialGUI = $true
$script:exitOnCancel = $true
$script:winFormsDPIAware = $true
}
default {
if ($_) {Write-Warning "[$_] Invalid profile ID specified. Valid ID are gui, console, winforms"}
}
}
} # END Set-ParamProfile
function Add-Assembly ([switch]$noConsole) {
$rawlist = [System.AppDomain]::CurrentDomain.GetAssemblies()
$asmlist = [System.Collections.Generic.List[object]]::new()
$asmlist.Add("System.dll")
$asmlist.Add($rawlist.where({$_.ManifestModule.Name -eq "System.Management.Automation.dll"},'first').Location)
$n = [System.Reflection.AssemblyName]::new("System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")
$null = [System.AppDomain]::CurrentDomain.Load($n)
$asmlist.Add($rawlist.where({$_.ManifestModule.Name -eq "System.Core.dll"},'first').Location)
if ($noConsole) {
$n = [System.Reflection.AssemblyName]::new("System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")
$null = [System.AppDomain]::CurrentDomain.Load($n)
$n = [System.Reflection.AssemblyName]::new("System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")
$null = [System.AppDomain]::CurrentDomain.Load($n)
$rawlist = [System.AppDomain]::CurrentDomain.GetAssemblies() # refresh list
$asmlist.Add($rawlist.where({$_.ManifestModule.Name -eq "System.Windows.Forms.dll"},'first').Location)
$asmlist.Add($rawlist.where({$_.ManifestModule.Name -eq "System.Drawing.dll"},'first').Location)
} else {
$conhostdll = $rawlist.where({$_.ManifestModule.Name -eq "Microsoft.PowerShell.ConsoleHost.dll"},'first')
if ($conhostdll) {$asmlist.Add(@($conhostdll.Location)[0])}
}
$asmlist
} # END Add-Assembly
function Export-Manifest ([string]$outputFile, [string]$type) {
# using: $DPIAware,$longPaths,$requireAdmin,$supportOS, $winFormsDPIAware
if ($type -match 'win32|core') {
$win32manifest = [System.Collections.Generic.List[object]]::new()
$win32manifest.AddRange(('<?xml version="1.0" encoding="UTF-8" standalone="yes"?>','<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">'))
if ($DPIAware -or $longPaths) {
$win32manifest.AddRange(('<application xmlns="urn:schemas-microsoft-com:asm.v3">',' <windowsSettings>'))
if ($DPIAware) {
$win32manifest.AddRange(('<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>','<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>'))
}
if ($longPaths) {
$win32manifest.Add('<longPathAware xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">true</longPathAware>')
}
$win32manifest.AddRange(('</windowsSettings>','</application>'))
}
if ($requireAdmin) {
$win32manifest.AddRange(('<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">','<security>','<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">','<requestedExecutionLevel level="requireAdministrator" uiAccess="false"/>','</requestedPrivileges>','</security>',' </trustInfo>'))
}
if ($supportOS) {
$win32manifest.AddRange(('<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">',' <application>','<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>','<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>',' <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>','<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>','<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/>','</application>','</compatibility>'))
}
$win32manifest.Add('</assembly>')
$manifest = if ($type -eq 'core') {[System.IO.Path]::ChangeExtension($outputFile,"manifest")} else {"$outputFile.win32manifest"}
([xml]"$win32manifest").Save($manifest)
#$win32manifest | Out-File -FilePath $manifest -Encoding UTF8
return
} # win32
elseif ($type -ne 'exe3') {return}
$configFileForEXE3 = [System.Collections.Generic.List[object]]::new()
$x = if ($winFormsDPIAware) {'4.7'} else {'4.0'}
$configFileForEXE3.AddRange(('<?xml version="1.0" encoding="utf-8" ?>',"<configuration><startup><supportedRuntime version=`"v4.0`" sku=`".NETFramework,Version=v$x`" /></startup>"))
if ($longPaths) {
$configFileForEXE3.Add('<runtime><AppContextSwitchOverrides value="Switch.System.IO.UseLegacyPathHandling=false;Switch.System.IO.BlockLongPaths=false" /></runtime>')
}
if ($winFormsDPIAware) {
$configFileForEXE3.Add('<System.Windows.Forms.ApplicationConfigurationSection><add key="DpiAwareness" value="PerMonitorV2" /></System.Windows.Forms.ApplicationConfigurationSection>')
}
$configFileForEXE3.Add('</configuration>')
$configFileForEXE3 | Out-File -FilePath "$outputFile.config" -Encoding UTF8
} # END Export-Manifest
function New-EmbedFileSection ([hashtable]$embedFiles, $compilerparam) {
if ($null -eq $embedFiles -or $embedFiles.Count -eq 0) {return}
Write-Host "Embedding $($embedFiles.Count) file(s)..."
$newline = [Environment]::NewLine
$sb = [System.Text.StringBuilder]::new()
$null = $sb.Append("string tgtFile = string.Empty, tgtDir = string.Empty;$newline")
$null = $embedFiles.GetEnumerator() |
ForEach-Object -begin {$i = 0} -process {
if (-not $_.Value -or -not $_.Name) {return}
$content = try {Compress-Text ([System.IO.File]::ReadAllText($_.Value))} catch {
Write-Warning "Error reading file '$($_.Value)'."
return
}
$sb.Append(('tgtFile = Environment.ExpandEnvironmentVariables(@"{0}");{2}if (string.Compare(".\\", 0, tgtFile, 0, 2) == 0) {{ tgtFile = System.AppDomain.CurrentDomain.BaseDirectory + tgtFile.Substring(2); }}{2}try {{ tgtDir = System.IO.Path.GetDirectoryName(tgtFile);{2}if (tgtDir != string.Empty) {{ System.IO.Directory.CreateDirectory(tgtDir); }}{2}System.IO.File.WriteAllText(tgtFile, Expand_text(@"{1}"),Encoding.UTF8);{2}}}{2}catch {{ throw new System.IO.IOException("Error creating ''" + tgtFile + "''\r\n"); }}{2}' -f $_.Name, $content, $newline))
$i++
}
if ($i) {$sb.ToString()}
} # END New-EmbedFileSection
function Get-SourceContent ([string]$inputFile, [switch]$resource) {
Write-Host "Reading the source..."
if ($noCompress) {
$scriptstring = if ($inputFile) {
[System.IO.File]::ReadAllText($inputFile) #, ([System.Text.Encoding]::utf8)
} else {$scriptblock.ToString()}
if (-not $scriptstring) {
Write-Error "Could not import the source."
exit
}
} else {
Write-Host 'Minifying...'
$param = @{NoTest = $true}
if ($basic) {$param['basic'] = $true}
if ($inputFile) {$param['Path'] = $inputFile} else {$param['Scriptblock'] = $scriptblock}
$scriptstring = Compress-ScriptBlock @param
if (-not $scriptstring) {
Write-Error "Could not compress the source."
exit
}
}
if ($inputFile) {
$hl = Get-Content -LiteralPath $inputFile -TotalCount 1 -ErrorAction 0
if ($hl -match '#Requires\s+-Version\s+[67]') {
Write-Warning "Input script contains `"$($matches[0])`" directive that may at least prevent correct working of the output executable."
}
}
Write-Host 'Compressing & encoding...' # the return of the king: nomore antivirus glitches
if ($resource) { # as embedResource; experimental
$tempinputFile = $env:TEMP,'ResourceFile.ps1' -join '\'
Compress-Text $scriptstring | Out-File -FilePath $tempinputFile -Encoding utf8
$tempinputFile
} else {
Compress-Text $scriptstring
}
} # END Get-SourceContent
function Compress-ScriptBlock {
[CmdletBinding(DefaultParameterSetName = 'File')]
[OutputType([string])]
[Alias('cmsb')]
param (
[Parameter(Position = 0, Mandatory, ParameterSetName = 'File')]
[ValidateNotNullOrEmpty()]
[alias('FilePath','FileName')][String] $Path,
[Parameter(Position = 0, Mandatory, ParameterSetName = 'ScriptBlock')]
[ValidateNotNullOrEmpty()]
[ScriptBlock] $ScriptBlock,
[Parameter(ParameterSetName = 'File')]
[Switch] $NoTest,
[Switch] $Basic # remove comments only
)
if ($Path) {
if (-not $NoTest) {if (-not (Test-Path -Path $Path)) {return}}
$scriptBlockString = [IO.File]::ReadAllText((Resolve-Path $Path).Path) #, ([System.Text.Encoding]::utf8)
$scriptBlock = [ScriptBlock]::Create($scriptBlockString)
} else {
# Convert the scriptblock to a string so that it can be referenced to extract script lines
$scriptBlockString = $scriptBlock.ToString()
}
if ([String]::IsNullOrEmpty($scriptBlockString)) {return}
# Tokenize the scriptblock and return all tokens except for comments/exclusions
$psperrors = $null
$tokens = @([System.Management.Automation.PSParser]::Tokenize($scriptBlock, [Ref]$psperrors)).where({$_.Type -ne 'Comment' -or $_.Content -match '^#Requires'})
if ($null -eq $tokens) {return}
#if ($psperrors.count) {}
# Parse options
$currentColumn = 1 # script line cursor
$newLine = $false # sequential newlines semaphore
$prevToken = $null # token cursor to look back
$tindex = 0 # token index to lookup ahead
$rxopt = [System.Text.RegularExpressions.RegexOptions]'Compiled,IgnoreCase'
$rxopertyp = [regex]::new('[,+=|/\$\-]',$rxopt)
$rxnewl = [regex]::new('^NewLine|^LineContin',$rxopt)
$rxgroup = [regex]::new('Group[SE]',$rxopt)
$rxtype = [regex]::new('GroupStart|NewLine|LineContin|Statement',$rxopt)
$rxhere = [regex]::new("^@['`"]",$rxopt)
# The show begins here
$strBuilder = foreach ($currentToken in $tokens) {
if ($rxnewl.IsMatch($currentToken.Type)) {
$currentColumn = 1
# Handle newlines. Sequential newlines are ignored in order to save space
if (-not $newLine) {
if ($Basic) {"`n"} #[Environment]::NewLine
else {
$t1 = $tindex+1
$next = $t1 -lt $tokens.count
# TODO: 1) ignore newline on condition 2) insert StatementSeparator on condition
if ($rxtype.IsMatch($prevToken.Type)) {}
elseif ($prevToken.Type -eq 16) {}
elseif ($next -and $tokens[$t1].Type -eq 13) {} #13=GroupEnd
elseif ($next -and $tokens[$t1].Type -eq 14 -and $tokens[$t1].Content -eq 'function') {}
elseif ($next -and $tokens[$t1].Type -eq 6 -and $prevToken.Type -eq 12 -and $prevToken.Content -eq '(') {}
elseif ($prevToken.Type -eq 11 -and $rxopertyp.IsMatch($prevToken.Content)) {} # 11=Operator
elseif ($prevToken.Content -eq ')' -and $next -and $tokens[$t1].Content -eq '{') {}
elseif ($prevToken.Type -eq 7 -and $next -and $tokens[$t1].Type -eq 7) {';'}
#?elseif ($prevToken.Type -eq 13 -and $currentToken.Type -ne 'keyword') {';'}
elseif ($next -and $tokens[$t1].Type -eq 6 -and -not $rxgroup.IsMatch($prevToken.Type)) {';'}
elseif ($prevToken.Type -eq 1) {';'} # 1=command
elseif ($prevToken.Type -eq 2) {' '} # 2=CommandParameter
else {"`n"} #[Environment]::NewLine
}
$newLine = $true
}
} else {
$newLine = $false
# Handle any indenting
if ($currentColumn -lt $currentToken.StartColumn) {
# Handle spaces in between tokens on the same line
# TODO: ignore extra spaces on condition
if ($currentColumn -ne 1) {
if ($Basic) {' '}
else {
if ($currentToken.Type -eq 11 -and $rxopertyp.IsMatch($currentToken.Content)) {} #11='Operator'
elseif ($prevToken.Type -eq 11 -and $rxopertyp.IsMatch($prevToken.Content)) {}
elseif ($currentToken.Type -eq 6 -and $prevToken.Type -eq 12 -and $prevToken.Content -eq '(') {}
elseif ($rxgroup.IsMatch($currentToken.Type) -or $prevToken.Type -eq 16) {} #16='StatementSeparator'
elseif ($currentToken.Type -eq 14 -and $currentToken.Content -ne 'in') {} #14='keyword'
else {' '}
}
}
}
$tokenBody = $scriptBlockString.Substring($currentToken.Start,$currentToken.Length)
$herestring = $currentToken.Type -eq 5 -and $rxhere.IsMatch($tokenBody)
# Handle multi-line strings 5='String'
if (-not $herestring -and $currentToken.Type -eq 5 -and $currentToken.EndLine -gt $currentToken.StartLine) {
$tokenBody.replace([Environment]::NewLine,'')
}
else { # Write out a regular token
$tokenBody
}
# Update current position in the column
$currentColumn = $currentToken.EndColumn
} # token type
$prevToken = $currentToken
$tindex++
} # token iterator
[string]::Concat($strBuilder) # -replace '(\r\n){2,}' .replace("`r`n`r`n","")
} # END Compress-ScriptBlock
function Compress-Text ([string[]] $InputObject) {
$memoryStream = [System.IO.MemoryStream]::new()
$compressionStream = [System.IO.Compression.DeflateStream]::new($memoryStream, [System.IO.Compression.CompressionMode]::Compress)
$streamWriter = [System.IO.StreamWriter]::new($compressionStream) # default is UTF8
try {
$streamWriter.Write($InputObject -join "`n") #[Environment]::newline
$streamWriter.Close()
[System.Convert]::ToBase64String($memoryStream.ToArray())
} catch {
Write-Error $_
}
$memoryStream.Dispose()
$streamWriter.Dispose()
} # END Compress-Text
function Resolve-PSHost ($parameters, [switch]$shellmode) {
if ($PSVersionTable.PSEdition -ne 'Core' -or $core) {return}
# PS7 host detected, start compiler in Windows PowerShell
if ($shellmode) {
Write-Warning 'Service mode: invalid set of parameters. Do not use "-shellmode" parameter.'
exit
}
Write-Warning "Incompatible platform detected. Reverting to Windows PowerShell."
# convert parameter table to command line form
$params = [System.Text.StringBuilder]::new()
$null = $parameters.GetEnumerator().foreach{
$key,$val = $_.Key,$_.Value
if ($val -is [System.Management.Automation.SwitchParameter]) {
if ($val.IsPresent) {$params.Append(" -$key")}
}
elseif ($val -is [string]) {
if ($val.contains(' ') -or [string]::IsNullOrEmpty($val)) {$val = "'$val'"}
$params.Append(" -$key $val")
}
elseif ($val -is [hashtable]) {
$ht = $val.GetEnumerator().foreach{"'{0}'='{1}';" -f $_.Key,$_.Value}
$params.Append(" -$key @{$ht}")
}
elseif ($val -is [array]) {
$ar = $val.foreach{
elseif ($_ -is [string] -and
($_.contains(' ') -or [string]::IsNullOrEmpty($_))) {"'$_'"} else {$_}
} -join ','
$params.Append(" -$key $ar")
}
elseif ($val -is [scriptblock]) {$params.Append(" -$key {$val}")}
else {
$params.Append(" -$key $val")
}
} # foreach
$cmd = (Get-Variable -Name MyInvocation -Scope 1).Value.MyCommand.Name # caller name
# dependency: module filename
& powershell.exe -NoLogo -Command "if (-not (Get-Command -Name $cmd -ErrorAction 0)) {. $PSScriptRoot\ps2exec.ps1}; &'$cmd' $($params.ToString()) -shellmode"
exit
} # END Resolve-PSHost
function Invoke-DomCompiler {
$topline = Get-Content -LiteralPath $inputFile -TotalCount 1 -ErrorAction 4
if ($topline -match '#Requires\s+-Version') { # temporarily stub
$ver = $topline.trim().split().where{$_}[2]
if (5 -lt $ver.split('.')[0]) {
Write-Warning "Incompatible target PowerShell host version ($ver) requirement detected. Current compiler mode is for Windows PowerShell only. Your script will fail to start."
}
}
# Generate compiler options
Write-Host "Preparing compiler..." -ForegroundColor Yellow
$compilerparam = [System.CodeDom.Compiler.CompilerParameters]::new((Add-Assembly -noConsole:$noConsole), $outputFile)
$compilerparam.GenerateInMemory = $false
$compilerparam.GenerateExecutable = $true
# Compiler command line options
# preference if a legacy param is used; backward compatibility
if ($x64 -and -not $x86) {$platform = 'x64'}
if ($x86 -and -not $x64) {$platform = 'x86'}
$target = if ($noConsole -or $conHost) {'winexe'} else {'exe'}
$manifestParam = if ($requireAdmin -or $DPIAware -or $supportOS -or $longPaths) {
'"/win32manifest:{0}.win32manifest"' -f $outputFile
Export-Manifest $outputFile -type 'win32'
}
if ($virtualize) {
Write-Host 'Application virtualization is activated, forcing x86 platform.'
$platform = 'x86'
$manifestParam = '/nowin32manifest'
}
$iconFileParam = if ($iconFile) {
'"/win32icon:{0}"' -f $iconFile
}
$optimizeParam = if (-not $prepareDebug) {'/optimize+'}
$compilerparam.CompilerOptions = "/platform:$platform /target:$target $optimizeParam $iconFileParam $manifestParam".TrimEnd()
# Debug settings
$compilerparam.IncludeDebugInformation = $prepareDebug
if ($prepareDebug) {
$compilerparam.TempFiles.KeepFiles = $true
}
# Create C# compiler
# generic dictionary type needed for CSharpCodeProvider
$dict = [Activator]::CreateInstance([System.Collections.Generic.Dictionary`2[string,string]])
###$dict = [System.Collections.Generic.Dictionary[string,string]]::new()
$dict['CompilerVersion'] = 'v4.0'
$csprov = [Microsoft.CSharp.CSharpCodeProvider]::new($dict)
$maincs = Get-MainProgramSource
Write-Host "Compiling..." -ForegroundColor Yellow
$cpstart = [datetime]::now
# NOTE: https://learn.microsoft.com/en-us/dotnet/api/system.codedom.compiler.codedomprovider.compileassemblyfromsource?view=net-11.0-pp#remarks
$cresult = $csprov.CompileAssemblyFromSource($compilerparam, $maincs)
$rt = Measure-Compiler $cpstart
#####Write-Host "Finishing..."
if ($cresult.Errors.Count -gt 0) {
Write-Host "Compiling runtime : $rt"
if (Test-Path -LiteralPath $outputFile) {
try {
Remove-Item -LiteralPath $outputFile -Force #-Verbose:$false
} catch {
Write-Error $_
}
}
Write-Error "Compilation failed. Use -verbose parameter to see details." -ErrorAction Continue
# omit useless enumeration of errors if -verbose not specified
if ($VerbosePreference -ne 0) {
$cresult.Output | Write-Host
$cresult.Errors | Write-Verbose
}
}
else {
if (Test-Path -LiteralPath $outputFile) {
Write-Host "Output file '$outputFile' successfully created" -ForegroundColor Green
Write-Host "Compiling runtime : $rt"
Measure-Output $outputFile
if ($configFile) {
Export-Manifest $outputFile -type 'exe3'
Write-Host "Config file for EXE created"
}
if ($run) {
Write-Host "Starting '$outputFile'..."
& $outputFile
}
}
else {
Write-Error "Output file '$outputFile' not created." -ErrorAction Continue
}
}
$csprov.Dispose() # release a compiler object
if ($prepareDebug) {
$dstSrc = if ($raw) {$workingDirectory}
else {[System.IO.Path]::GetDirectoryName($outputFile)}
$cresult.TempFiles | Where-Object {$_ -match "\.cs$"} |
Select-Object -First 1 | ForEach-Object {
Write-Host "Source file for debug copied: '$dstSrc\main.cs'"
Copy-Item -Path $_ -Destination "$dstSrc\main.cs" -Force
}
}
Write-Host 'Removing temp files...'
$cresult.TempFiles | Remove-Item -Force -ErrorAction SilentlyContinue #-Verbose:$false
if ($requireAdmin -or $DPIAware -or $supportOS -or $longPaths) {
if (Test-Path -LiteralPath "$outputFile.win32manifest") {
Remove-Item -LiteralPath "$outputFile.win32manifest" -Force #-Verbose:$false
}
}
} # END Invoke-DomCompiler
#region PS.Core support
function Invoke-DotnetCompiler {
# Some C# hints taken from https://github.com/FabienTschanz/PS2EXE.Core
# Project-based compilation used
##api.nuget.org -Port 443
$pp = $global:ProgressPreference # fix PS.Core flaw
$ProgressPreference = $global:ProgressPreference = 'SilentlyContinue'
if (-not (Test-NetConnection -ComputerName nuget.org -InformationLevel Quiet -ErrorAction 0)) {
Write-Warning 'Check your internet connection or DNS/Firewall settings. PowerShell Core support requires the public NuGet repository available at compiling runtime.'
$ProgressPreference = $global:ProgressPreference = $pp
return
}
$ProgressPreference = $global:ProgressPreference = $pp
if (-not $solo) { # if self-contained mode
Write-Warning ".NET versions of the compiled program and the target system must match."
}
Write-Host "Preparing compiler..." -ForegroundColor Yellow
$dotnethost = Resolve-DotnetHost -nocache:$nocache
if ($null -eq $dotnethost) {
Write-Warning "Could not obtain .NET information."
return
}
$dotnetversion,$pssdk = Resolve-DotnetRequirement $dotnethost $dotnetversion
if (-not $dotnetversion) {return}
# Prepare and save the project to build
$csProjName = [System.IO.Path]::GetFileNameWithoutExtension($inputFile)
$outputDirectory = [System.IO.Path]::GetDirectoryName($outputFile)
$releaseDir = Join-Path -Path $outputDirectory -ChildPath 'Release'
$requiresWinForms = [System.IO.File]::ReadAllText($inputFile) -match 'Add-Type\s+-AssemblyName\s+(System\.Windows\.Forms|PresentationFramework|\.showdialog\(\))'
$includenativelibs = $PSVersionTable.PSVersion.Major -lt 6 -or -not $dotnethost.local.psver -or [version]$dotnethost.local.psver -lt [version]'7.6'
$targetOS = if (-not $targetOS) {'win'}
elseif ($targetOS -match '^windows|^win|^w') {'win'}
elseif ($targetOS -match '^linux|^lx?') {'linux'}
elseif ($targetOS -match '^macos|^mac|^osx|^m') {'osx'}
else {
'win'
Write-Warning 'Incorrect target OS specified. Reset to Windows. Correct values are windows, linux, mac'
}
$platform = if ($arm) {'arm64'}
elseif ($targetOS -match 'linux|osx') {'x64'}
elseif ($platform -eq 'x64') {'x64'} else {'x86'}
$manifestFileName = if ($requireAdmin -or $DPIAware -or $supportOS -or $longPaths) {
#Split-Path "$outputFile.win32manifest" -Leaf
#Export-Manifest $outputFile -type 'win32'
Split-Path ([System.IO.Path]::ChangeExtension($outputFile,"manifest")) -Leaf
Export-Manifest $outputFile -type 'core'
}
$outputType = if ($targetOS -eq 'win') {
if ($noConsole -or $CredentialGUI -or $requiresWinForms) {
'WinExe'
$dotnetOS = '-windows'
} else {'Exe'}
}
$psdnet = [System.Runtime.InteropServices.RuntimeInformation]::FrameworkDescription -split ' '
[pscustomobject][ordered]@{
'Type' = 'Local'
'.NET/SDK' = $(& dotnet.exe --version)
'PowerShell' = $dotnethost.local.psver
'PS .NET' = if ($PSVersionTable.PSVersion.Major -lt 6) {$psdnet[2]} else {$psdnet[1]}
'OS Id' = [System.Runtime.InteropServices.RuntimeInformation]::OSDescription -replace 'microsoft '
'Platform' = [System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture.tostring().tolower()
},
[pscustomobject][ordered]@{
'Type' = 'Target'
'.NET/SDK' = $dotnetversion
'PowerShell' = $dotnethost.local.psver
'PS .NET' = ''
'OS Id' = $targetOS
'Platform' = $platform
} | Format-Table
$l = [version]$(& dotnet.exe --version)
$t = [version]$dotnetversion
$status = if ($t.Major -eq $l.Major -and $t.Minor -eq $l.Minor) {'OK'} else {'Not match'}
Write-Host ".NET SDK requirements: $status"
if ($status -ne 'OK') {return}
$appIcon = if ($iconFile) {[System.IO.Path]::GetFileName($iconFile)}
if ($appIcon -and -not (Test-Path $outputDirectory\$appIcon)) {
Copy-Item -LiteralPath $iconFile -Destination $outputDirectory
}
$useWindowsForms = $noConsole -or $CredentialGUI -or $requiresWinForms
# EXPERIMENTAL
$constants = '' # already set in CS template
# +PublishSingleFile (default) includes only the application and third-party assemblies
# +SelfContained includes .NET runtime, output size up to 6x
# +Trimmed requires SelfContained
# -AOT requires SelfContained; unreliabale/useless
# -ReadyToRun heavily increases compile time (6-10+x), output size (2-3x), and start time (1.5+x) without any valueble effect; useless
# [File-based apps: Self-contained + Native AOT + .NET10+] - a different way to compile
$trimmed = $readyToRun = $aot = $false
$selfContained = $solo
if ($trimmed -and -not $selfContained) {$selfContained = $trimmed}
##if ($aot -and -not $selfContained) {}
# Generate project files; !comes after all: parameters already must be set!
$csProjPath = "$outputDirectory\$csProjName.csproj"
$csProjSource = "$outputDirectory\$csProjName.cs"
Set-CSProject $csProjPath $csProjSource
if ($prepareDebug) {
Write-Host "`nPublish resources`n-----------------"
Write-Host (
"CS File : $csProjName.cs",
"Project File : $csProjName.csproj",
"Output Path : $outputDirectory`n"
) -Separator "`n"
}
Write-Host "`nPublish profile`n---------------"
Write-Host (
"SingleFile : (default)",
"SelfContained : $selfContained",
"Trimmed : $trimmed",
"Aot : (disabled)",
"ReadyToRun : (disabled)"
) -Separator "`n"
# enclose paths in double quotes?
$dotnetArgs = 'publish', $csProjPath, '-c', 'Release', '-o', $releaseDir
# TODO: better Verbose
if ($VerbosePreference -eq 'Continue') {
$dotnetArgs += '-v', 'd'
} else {
$dotnetArgs += '-v', 'q', '--property', 'WarningLevel=0', '/clp:ErrorsOnly'
}
Write-Host "`nCompiling..." -ForegroundColor Yellow
$dnstart = [datetime]::now
$dotnetOutput = & dotnet.exe @dotnetArgs 2>&1
$rt = Measure-Compiler $dnstart
$compileresult = $LASTEXITCODE
if ($compileresult -ne 0) {
Write-Host "Compiling runtime : $rt"
$errorLines = $dotnetOutput | Where-Object {$_ -is [System.Management.Automation.ErrorRecord] -or $_ -match 'error'}
$errorDetail = if ($errorLines) {$errorLines} else {$dotnetOutput}
throw "dotnet publish failed with exit code $LASTEXITCODE.`n$(($errorDetail | Out-String).Trim())"
}
# Publish "exe", remove temp files
##Write-Host "Successfully built project '$csProjName' in '$outputDirectory'." -ForegroundColor Green
Write-Host "Output file '$outputFile' successfully created" -ForegroundColor Green
if (-not $selfContained) {
Write-Host "Target runtime environment will require .NET SDK version $dotnetversion. To avoid framework dependency use parameter -solo, but this will increase compilation time and output file size." -ForegroundColor Yellow
}
Write-Host "Compiling runtime : $rt"
Measure-Output "$releaseDir\$csProjName.exe"
# TODO: better debug info
# what about linux and macos? fuck it all
Copy-Item -Path $releaseDir\$csProjName.exe -Destination $outputDirectory -Force -ErrorAction 0
Write-Host 'Removing temp files...'
if (-not $prepareDebug) {
Remove-Item -Path $releaseDir -Recurse -Force -ErrorAction 0
Remove-Item -Path $outputDirectory\bin -Recurse -Force -ErrorAction 0
Remove-Item -Path $outputDirectory\obj -Recurse -Force -ErrorAction 0
Remove-Item -Path $csProjPath -Force -ErrorAction 0
Remove-Item -Path $csProjSource -Force -ErrorAction 0
}
if ($appIcon -and "$outputDirectory\$appIcon" -ne $iconFile) {
Remove-Item -Path $outputDirectory\$appIcon -Force -ErrorAction 0
}
if ($manifestFileName -and -not $prepareDebug) {
Remove-Item -Path $outputDirectory\$manifestFileName -Force -ErrorAction 0
}
if ($run) {
Write-Host "Starting '$outputFile'..."
& $outputFile
}
} # END Invoke-DotnetCompiler
function Resolve-DotnetHost ([switch]$nocache) {
$dnlocal = try {
(& dotnet.exe --list-sdks) | ForEach-Object {$_.split('.')[0,1] -join '.'} | Sort-Object {[version]$_} -Descending
} catch {}
if (-not $dnlocal) {
throw "The .NET SDK is not installed. Please install it from https://dotnet.microsoft.com/en-us/download."
#Install-Dotnet -force
}
# TODO: what if internet or github unaccessable? soft workaround: caching
$dncache = "$env:TEMP\dnrelease.json"
$expired = if (Test-Path $dncache) {
$lwt = Get-ItemProperty -LiteralPath $dncache -Name LastWriteTime
([datetime]::now - $lwt.LastWriteTime).totaldays -gt 2
} else {$true}
if ($expired -or $nocache) {
$getrelease = {param($url) (Invoke-WebRequest -Uri $url -UseBasicParsing -TimeoutSec 15 -ErrorAction 0 -Verbose:$false).links | Where-Object href -match '/releases/tag' | ForEach-Object {$_.href.split('/')[-1].Substring(1)}}
$p = $ProgressPreference
$ProgressPreference = 'SilentlyContinue'
$dnrel = & $getrelease 'https://github.com/dotnet/core/tags' | Sort-Object {[version]$_.split('-')[0]} -Unique -Descending
$psrel = & $getrelease 'https://github.com/PowerShell/PowerShell/tags' | Sort-Object -Unique -Descending
$ProgressPreference = $p
$psmap = [ordered]@{}
$dnrel | Group-Object {($_.split('.')[0,1] -join '.')} | ForEach-Object {
$psmap[$_.name] = if ($_.group -match 'prev') {@{preview = $true}} else {@{}}
}
$psrel | Group-Object {($_.split('.')[0,1] -join '.')} |
ForEach-Object -begin {$i = 0} -process {
$v = @($psmap.Keys)[$i]
$psmap[$v]['branch'] = $_.name
$psmap[$v]['psver'] = $_.group[0]
$i++
}
$latest = if ($PSVersionTable.PSVersion.Major -lt 6) {
try {((pwsh.exe -Version) -split ' ')[1]} catch {}
} else {$PSVersionTable.PSVersion.ToString()}
$psmap['local'] = @{
dotnet = @($dnlocal)
psver = $latest
}
##Remove-Item -LiteralPath $dncache -Force -ErrorAction 0
$psmap | ConvertTo-Json -Compress | Out-File $dncache -Encoding utf8 -ErrorAction 0
[pscustomobject]$psmap
} else {
Get-Content $dncache -Raw | ConvertFrom-Json -ErrorAction 0
}
} # END Resolve-DotnetHost
function Resolve-DotnetRequirement ($dotnethost, $dotnetversion) {
$supportFrame = $dotnethost.psobject.properties.name | Where-Object {$_ -ne 'local'} | Sort-Object {[version]$_} -Descending
if ($supportFrame) {
$supportFrame[0] = [int]$supportFrame[0]
$supportFrame[-1] = [int]$supportFrame[-1]
} else {
$supportFrame = 8,11 # fallback
}
$matchlocal = $dotnethost.local.dotnet | Where-Object {([version]$_).major -ge $supportFrame[-1]}
[string[]]$listlocal = $dotnethost.local.dotnet | Sort-Object {[version]$_} -Descending
if ($dotnethost.local.dotnet -and -not $matchlocal) {
Write-Warning ".NET version $($supportFrame[-1]) or higher is required. Current version is $($listlocal -join ', '). Install latest from https://dotnet.microsoft.com/en-us/download. .NET Core lifecycle: https://dotnet.microsoft.com/en-us/platform/support/policy/dotnet-core#lifecycle"
return
}
# TODO: auto-determine minimum dotnet version
$mindn,$minps = '10.0','7.6.*' # magic numbers, soft defaults, should be revisited in 2028
if (-not $dotnetversion) {
$dotnetversion = if ($dotnethost.local.dotnet) {$listlocal[0]} else {$mindn}
} elseif ($dotnetversion -eq 'latest') {
$dotnetversion = if ($dotnethost.count) {@($dotnethost.psobject.properties.name)[0]} else {$mindn}
}
if ($dotnetversion -notmatch '\.') {$dotnetversion = "$dotnetversion.0"}
$dotnetversion = $dotnetversion -replace '[dotnet]'
$dotnetversion = $dotnetversion.split('.')[0,1] -join '.'
if ($supportFrame[-1] -ge $dotnetversion -or $supportFrame[0] -le $dotnetversion) {
$dotnetversion = if ($dotnethost.local.dotnet) {$listlocal[0]} else {$mindn}
}
$pssdk = if ($dotnethost.local.psver) {$dotnethost.local.psver} else {$minps}
if ($pssdk -notmatch '\*') {
$pssdk = '{0}.{1}.*' -f $pssdk.split('.')[0,1]
}
$dotnetversion,$pssdk
} # END Resolve-DotnetRequirement
function Install-Dotnet ([switch]$force) {
# https://github.com/actions/setup-dotnet
if (-not $force -and
$null -ne (Get-Command "dotnet.exe" -ErrorAction SilentlyContinue) -and
$(& dotnet.exe --version) -and $LASTEXITCODE -eq 0) {
Write-Host ".NET Core SDK already installed"; $null
}
else {
$DotNetInstallFile = "$env:TEMP\dotnet-install.ps1"
$DotNetInstallUrl = "https://dot.net/v1/dotnet-install.ps1"
$p = [Net.ServicePointManager]::SecurityProtocol
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
try {
[System.Net.WebClient]::new().DownloadFile($DotNetInstallUrl, $DotNetInstallFile)
} catch {}
[Net.ServicePointManager]::SecurityProtocol = $p
if (Test-Path $DotNetInstallFile) {
$dnpath = "$env:ProgramFiles\dotnet"
& powershell.exe -Nologo -ExecutionPolicy bypass -File $DotNetInstallFile -InstallDir $dnpath
if ($LASTEXITCODE) {$LASTEXITCODE} else {
if ($env:PATH -notmatch "$([regex]::Escape($dnpath))") {
[System.Environment]::SetEnvironmentVariable('PATH',"$env:PATH;$dnpath", 'Machine')
Write-Host ".NET Core SDK version: $(& dotnet.exe --version)"
}; $true
}
} else {
Write-Warning "Error downloading installation script. Try again later or download and install .NET SDK manually from https://dotnet.microsoft.com/en-us/download"; $false
}
}
} # END Install-Dotnet
function Set-CSProject ([string]$csProjPath, [string]$csProjSource) {
# https://learn.microsoft.com/en-us/dotnet/core/project-sdk/msbuild-props#targetframeworks
# https://learn.microsoft.com/en-us/dotnet/standard/frameworks
# https://learn.microsoft.com/en-us/dotnet/core/deploying/single-file/overview?tabs=cli#native-libraries
if ($constants) {$constants = "<DefineConstants>$constants</DefineConstants>"}
if ($appIcon) {$appIcon = "<ApplicationIcon>$appIcon</ApplicationIcon>"}
if ($manifestFileName) {$manifestFileName = "<ApplicationManifest>$manifestFileName</ApplicationManifest>"}
#$includenativelibs,$includeallcontent = if ($includenativelibs) {
# "<IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>",
# "<IncludeAllContentForSelfExtract>true</IncludeAllContentForSelfExtract>"
#} else {'',''}
$csproj = [xml]@"
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>$outputType</OutputType>
<TargetFramework>net${dotnetversion}$dotnetOS</TargetFramework>
<!--does not work <TargetFrameworks>net10.0;net9.0;net8.0</TargetFrameworks>-->
<RuntimeIdentifier>$targetOS-$platform</RuntimeIdentifier>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<PublishSingleFile>True</PublishSingleFile>
<IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>
<IncludeAllContentForSelfExtract>true</IncludeAllContentForSelfExtract>
<SelfContained>$selfContained</SelfContained>
<UseWindowsForms>$useWindowsForms</UseWindowsForms>
<UseWPF>$useWindowsForms</UseWPF>
<PublishTrimmed>$trimmed</PublishTrimmed>
<!--<TrimMode></TrimMode>-->
<PublishReadyToRun>$readyToRun</PublishReadyToRun>
<PublishAot>$aot</PublishAot>
<InvariantGlobalization>False</InvariantGlobalization>
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
<GenerateAssemblyTitleAttribute>false</GenerateAssemblyTitleAttribute>
<GenerateAssemblyVersionAttribute>false</GenerateAssemblyVersionAttribute>
<GenerateAssemblyFileVersionAttribute>false</GenerateAssemblyFileVersionAttribute>
<StartupObject>PSRunner.MainApp</StartupObject>
<SatelliteResourceLanguages>en-US;en</SatelliteResourceLanguages>
$constants
$appIcon
$manifestFileName
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.PowerShell.SDK" Version="$pssdk"/>
</ItemGroup>
<!-- Preserve PowerShell reflection targets from trimming
<ItemGroup Condition="'$trimmed' == 'True'">
<TrimmerRootAssembly Include="System.Management.Automation"/>
<TrimmerRootAssembly Include="Microsoft.PowerShell.Commands.Diagnostics"/>
<TrimmerRootAssembly Include="Microsoft.PowerShell.Commands.Management"/>
<TrimmerRootAssembly Include="Microsoft.PowerShell.Commands.Utility"/>
<TrimmerRootAssembly Include="Microsoft.PowerShell.ConsoleHost"/>
<TrimmerRootAssembly Include="Microsoft.PowerShell.Security"/>
<TrimmerRootAssembly Include="Microsoft.WSMan.Management"/>
</ItemGroup> -->
<!-- If AOT compilation enabled and .NETSDK > 8, include System.Formats.Nrbf
<ItemGroup Condition="'$aot' == 'True' And `$([MSBuild]::GetTargetFrameworkIdentifier('`$(TargetFramework)')) == '.NETCoreApp' And `$([MSBuild]::GetTargetFrameworkVersion('`$(TargetFramework)')) >= '8.0'">
<PackageReference Include="System.Formats.Nrbf" Version="10.*"/>
</ItemGroup> -->
</Project>
"@
# Idempotency: write changes only, spend time/memory/CPU to save disk
Write-Host 'Checking for input changes...'
$cssource = Get-MainProgramSource
$hashPath = "$env:TEMP\ps2exe.hash"
$teststring = "$cssource|$useWindowsForms|$appIcon|$outputType|$dotnetversion|$dotnetOS|$targetOS|$platform|$includenativelibs|$selfContained|$trimmed|$readyToRun|$aot|$constants|$manifestFileName"
$newhash = (Get-FileHash -InputStream ([IO.MemoryStream]::new([Text.Encoding]::UTF8.GetBytes($teststring))) -Algorithm SHA256).Hash
####$newhash = [System.BitConverter]::ToString([System.Security.Cryptography.SHA256]::Create().ComputeHash([System.Text.Encoding]::UTF8.GetBytes($teststring))) #.Replace('-', '')
$dirty = if (-not $nocache -and (Test-Path $hashPath) -and (Test-Path $outputFile) -and (Test-Path $csProjPath)) {
$hash = Get-Content $hashPath -Raw
$newhash -ne $hash.trim()
} else {$true}
if ($dirty) {
$newhash | Out-File -FilePath $hashPath -Encoding UTF8 -Force
$csproj.save($csProjPath)
$cssource | Out-File -FilePath $csProjSource -Encoding utf8
} else {
Write-Warning "No changes found, nothing to do. Use -nocache parameter to omit checking for changes."
exit
}
# Something went wrong
$csProjName = [System.IO.Path]::GetFileNameWithoutExtension($csProjPath)
if (-not (Test-Path $csProjSource,$csProjPath -ErrorAction 0)) {
Write-Warning "Missing CS project file(s). Check it out: '$csProjName.csproj', '$csProjName.cs'. Resolve all issues and try again."
exit
}
# multiple CS files result in conflicts & inexplicable errors
if ((Get-ChildItem "$([System.IO.Path]::GetDirectoryName($csProjPath))\*.cs").count -gt 1) {
Write-Warning "There must be just one CS file: '$csProjName.cs'. Remove non relevant files and try again."
exit
}
} # END Set-CSProject
#endregion PS.Core support
function Measure-Compiler ($start) {
$diff = [datetime]::now - $start
$msfractional = $diff.TotalMilliseconds - [Math]::Truncate($diff.TotalMilliseconds)
$ms = $msfractional + $diff.Milliseconds
if ($diff.Hours) {'{0}h:{1}m:{2}s:{3:N2}ms' -f $diff.Hours,$diff.Minutes,$diff.Seconds,$ms}
elseif ($diff.Minutes) {'{0}m:{1}s:{2:N2}ms' -f $diff.Minutes,$diff.Seconds,$ms}
elseif ($diff.Seconds) {'{0}s:{1:N2}ms' -f $diff.Seconds,$ms}
else {'{0:N2}ms' -f $diff.TotalMilliseconds}
} # END Measure-Compiler
function Measure-Output ($filepath) {
$filesize = (Get-ItemProperty -LiteralPath $filepath -Name Length).Length
$capacity,$unit = if ($filesize/1MB -le 1) {1KB,'KB'} else {1MB,'MB'}
Write-Host "Output file size : $('{0} {1}' -f ([math]::round($filesize/$capacity,1)),$unit)"
} # END Measure-Output
function Get-DefaultIcon {
# NOTE: this is a work around because in Windows Powershell [System.Drawing.Icon] class has a limited functionality
$code = '
using System;
using System.Drawing;
using System.Runtime.InteropServices;
using System.IO;
public class PngIconConverter
{
public static bool Convert(System.IO.Stream input_stream, System.IO.Stream output_stream, int size, bool keep_aspect_ratio = false)
{
System.Drawing.Bitmap input_bit = (System.Drawing.Bitmap)System.Drawing.Bitmap.FromStream(input_stream);
if (input_bit != null)
{
int width, height;
if (keep_aspect_ratio)
{
width = size;
height = input_bit.Height / input_bit.Width * size;
}
else
{
width = height = size;
}
System.Drawing.Bitmap new_bit = new System.Drawing.Bitmap(input_bit, new System.Drawing.Size(width, height));
if (new_bit != null)
{
System.IO.MemoryStream mem_data = new System.IO.MemoryStream();
new_bit.Save(mem_data, System.Drawing.Imaging.ImageFormat.Png);
System.IO.BinaryWriter icon_writer = new System.IO.BinaryWriter(output_stream);
if (output_stream != null && icon_writer != null)
{
icon_writer.Write((byte)0);
icon_writer.Write((byte)0);
icon_writer.Write((short)1);
icon_writer.Write((short)1);
icon_writer.Write((byte)width);
icon_writer.Write((byte)height);
icon_writer.Write((byte)0);
icon_writer.Write((byte)0);
icon_writer.Write((short)0);
icon_writer.Write((short)32);
icon_writer.Write((int)mem_data.Length);
icon_writer.Write((int)(6 + 16));
icon_writer.Write(mem_data.ToArray());
icon_writer.Flush();
return true;
}
}
return false;
}
return false;
}
public static bool Convert(string input_image, string output_icon, int size, bool keep_aspect_ratio = false)
{
System.IO.FileStream input_stream = new System.IO.FileStream(input_image, System.IO.FileMode.Open);
System.IO.FileStream output_stream = new System.IO.FileStream(output_icon, System.IO.FileMode.OpenOrCreate);
bool result = Convert(input_stream, output_stream, size, keep_aspect_ratio);
input_stream.Close();
output_stream.Close();
return result;
}
}'
try {Add-Type -TypeDefinition $code -ReferencedAssemblies System.Drawing, System.IO -ErrorAction SilentlyContinue} catch {}
$iconsource = 'C:\windows\system32\WindowsPowerShell\v1.0\PowerShell.exe'
$tempiconFile = "$env:TEMP\ps2exedefault.png"
$iconFile = "$env:TEMP\ps2exedefault.ico"
[System.Drawing.Icon]::ExtractAssociatedIcon($iconsource).ToBitMap().Save($tempiconFile)
if ([PngIconConverter]::Convert($tempiconFile,$iconFile,32,$true)) {$iconFile}
Remove-Item $tempiconFile -Force -ErrorAction SilentlyContinue
} # END Get-DefaultIcon
function Get-MainProgramSource {
$sourceScript = Get-SourceContent $inputFile
$embedFileSection = New-EmbedFileSection $embedFiles $compilerparam
$culture = if ($lcid -and $lcid -as [int]) {
@"
System.Threading.Thread.CurrentThread.CurrentCulture = System.Globalization.CultureInfo.GetCultureInfo($lcid);
System.Threading.Thread.CurrentThread.CurrentUICulture = System.Globalization.CultureInfo.GetCultureInfo($lcid);
"@
}
# template {{preprocessor}}
$preprocessor = [System.Collections.Generic.List[object]]::new()
# all uppercase? .toupper()
'noConsole noOutput noError conHost unicode credentialGUI winFormsDpiAware noVisualStyles exitOnCancel title'.split() | ForEach-Object {
if (Get-Variable -Name $_ -ValueOnly -ErrorAction 0) {$preprocessor.Add("#define $_")}
}
if ($core) {$preprocessor.Add("#define psCore")}
if (-not $mta) {$preprocessor.Add("#define STA")}
if (-not $version) {$version = '0.0.0.0'}
<#$metadata = [System.Collections.Generic.List[string]]::new()
if ($title) {$metadata.Add(('[assembly:AssemblyTitle("{0}")]' -f $title))}
if ($copyright) {$metadata.Add(('[assembly:AssemblyCopyright("{0}")]' -f $copyright))}
if ($trademark) {$metadata.Add(('[assembly:AssemblyTrademark("{0}")]' -f $trademark))}
if ($version) {
$metadata.Add(('[assembly:AssemblyVersion("{0}")]' -f $version))
$metadata.Add(('[assembly:AssemblyFileVersion("{0}")]' -f $version))
}
if ($description) {$metadata.Add(('[assembly:AssemblyDescription("{0}")]' -f $description))}
if ($company) {$metadata.Add(('[assembly:AssemblyCompany("{0}")]' -f $company))}
$metadata = $metadata -join "`n"#>
# C# source; minimal compression: strip empty lines, leading whitespace, and oneliner comments
# multiline comments: $main -replace '(?m)/\*(.|[\r\n])*?\*/'
@"
// Simple PowerShell host created by Ingo Karstein (http://blog.karstein-consulting.com)
// Reworked and GUI support by Markus Scholtes
// REFACTOR: using preprocessor directives instead of inline PS code; translations from german
// ENHANCEMENT: added source encoding, minifying, and compression
// Support for PS.Core taken from https://github.com/FabienTschanz/PS2EXE.Core
$($preprocessor -join "`n")
using System;
using System.IO;
using System.IO.Compression;
using System.Collections.Generic;
using System.Text;
//using System.Text.RegularExpressions;
using System.Management.Automation;
using System.Management.Automation.Runspaces;
using System.Management.Automation.Host;
using System.Globalization;
using System.Security;
using System.Reflection;
using System.Runtime.InteropServices;
#if noConsole
using System.Windows.Forms;
using System.Drawing;
#endif
#if winFormsDpiAware
using System.Runtime.Versioning;
[assembly:TargetFrameworkAttribute(".NETFramework,Version=v4.7,Profile=Client",FrameworkDisplayName=".NET Framework 4.7")]
#endif
[assembly:AssemblyTitle("$title")]
[assembly:AssemblyProduct("$product")]
[assembly:AssemblyCopyright("$copyright")]
[assembly:AssemblyTrademark("$trademark")]
[assembly:AssemblyVersion("$version")]
[assembly:AssemblyFileVersion("$version")]
// not displayed in details tab of properties dialog, but embedded to file
[assembly:AssemblyDescription("$description")]
[assembly:AssemblyCompany("$company")]
namespace PSRunner
{
#if noConsole || credentialGUI
internal class Credential_Form
{
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private struct CREDUI_INFO
{
public int cbSize;
public IntPtr hwndParent;
public string pszMessageText;
public string pszCaptionText;
public IntPtr hbmBanner;
}
[Flags]
enum CREDUI_FLAGS
{
INCORRECT_PASSWORD = 0x1,
DO_NOT_PERSIST = 0x2,
REQUEST_ADMINISTRATOR = 0x4,
EXCLUDE_CERTIFICATES = 0x8,
REQUIRE_CERTIFICATE = 0x10,
SHOW_SAVE_CHECK_BOX = 0x40,
ALWAYS_SHOW_UI = 0x80,
REQUIRE_SMARTCARD = 0x100,
PASSWORD_ONLY_OK = 0x200,
VALIDATE_USERNAME = 0x400,
COMPLETE_USERNAME = 0x800,
PERSIST = 0x1000,
SERVER_CREDENTIAL = 0x4000,
EXPECT_CONFIRMATION = 0x20000,
GENERIC_CREDENTIALS = 0x40000,
USERNAME_TARGET_CREDENTIALS = 0x80000,
KEEP_USERNAME = 0x100000,
}
public enum CredUI_ReturnCodes
{
NO_ERROR = 0,
ERROR_CANCELLED = 1223,
ERROR_NO_SUCH_LOGON_SESSION = 1312,
ERROR_NOT_FOUND = 1168,
ERROR_INVALID_ACCOUNT_NAME = 1315,
ERROR_INSUFFICIENT_BUFFER = 122,
ERROR_INVALID_PARAMETER = 87,
ERROR_INVALID_FLAGS = 1004,
}
[DllImport("credui", CharSet = CharSet.Unicode)]
private static extern CredUI_ReturnCodes CredUIPromptForCredentials(ref CREDUI_INFO credinfo,
string targetName,
IntPtr reserved1,
int iError,
StringBuilder userName,
int maxUserName,
StringBuilder password,
int maxPassword,
[MarshalAs(UnmanagedType.Bool)] ref bool pfSave,
CREDUI_FLAGS flags);
public class User_Pwd
{
public string User = string.Empty;
public string Password = string.Empty;
public string Domain = string.Empty;
}
internal static User_Pwd PromptForPassword(string caption, string message, string target, string user, PSCredentialTypes credTypes, PSCredentialUIOptions options)
{
// Initializing flags and variables
StringBuilder userPassword = new StringBuilder("", 128), userID = new StringBuilder(user, 128);
CREDUI_INFO credUI = new CREDUI_INFO();
if (!string.IsNullOrEmpty(message)) credUI.pszMessageText = message;
if (!string.IsNullOrEmpty(caption)) credUI.pszCaptionText = caption;
credUI.cbSize = Marshal.SizeOf(credUI);
bool save = false;
CREDUI_FLAGS flags = CREDUI_FLAGS.DO_NOT_PERSIST;
if ((credTypes & PSCredentialTypes.Generic) == PSCredentialTypes.Generic)
{
flags |= CREDUI_FLAGS.GENERIC_CREDENTIALS;
if ((options & PSCredentialUIOptions.AlwaysPrompt) == PSCredentialUIOptions.AlwaysPrompt)
{
flags |= CREDUI_FLAGS.ALWAYS_SHOW_UI;
}
}
// prompt the user for their password, graphical prompt
CredUI_ReturnCodes returnCode = CredUIPromptForCredentials(ref credUI, target, IntPtr.Zero, 0, userID, 128, userPassword, 128, ref save, flags);
if (returnCode == CredUI_ReturnCodes.NO_ERROR)
{
User_Pwd ret = new User_Pwd();
ret.User = userID.ToString();
ret.Password = userPassword.ToString();
ret.Domain = "";
return ret;
}
return null;
}
}
#endif
internal class MainModuleRawUI : PSHostRawUserInterface
{
#if noConsole
// Storage console colors in GUI output is read and set, but not currently used (for future use)
private ConsoleColor GUIBackgroundColor = ConsoleColor.White;
private ConsoleColor GUIForegroundColor = ConsoleColor.Black;
#if !title
private string GUITitle = System.AppDomain.CurrentDomain.FriendlyName;
#else
private string GUITitle = Assembly.GetExecutingAssembly().GetCustomAttribute<AssemblyTitleAttribute>().Title;
#endif
#else
const int STD_OUTPUT_HANDLE = -11;
//CHAR_INFO struct, which was a union in the old days
// so we want to use LayoutKind.Explicit to mimic it as closely as we can
[StructLayout(LayoutKind.Explicit)]
public struct CHAR_INFO
{
[FieldOffset(0)]
internal char UnicodeChar;
[FieldOffset(0)]
internal char AsciiChar;
[FieldOffset(2)] //2 bytes seems to work properly
internal UInt16 Attributes;
}
//COORD struct
[StructLayout(LayoutKind.Sequential)]
public struct COORD
{
public short X;
public short Y;
}
//SMALL_RECT struct
[StructLayout(LayoutKind.Sequential)]
public struct SMALL_RECT
{
public short Left;
public short Top;
public short Right;
public short Bottom;
}
// Reads character and color attribute data from a rectangular block of character cells in a console screen buffer, and the function writes the data to a rectangular block at a specified location in the destination buffer.
[DllImport("kernel32.dll", EntryPoint = "ReadConsoleOutputW", CharSet = CharSet.Unicode, SetLastError = true)]
internal static extern bool ReadConsoleOutput(
IntPtr hConsoleOutput,
// This pointer is treated as the origin of a two-dimensional array of CHAR_INFO structures whose size is specified by the dwBufferSize parameter.
[MarshalAs(UnmanagedType.LPArray), Out] CHAR_INFO[,] lpBuffer,
COORD dwBufferSize,
COORD dwBufferCoord,
ref SMALL_RECT lpReadRegion);
// Writes character and color attribute data to a specified rectangular block of character cells in a console screen buffer.
// The data to be written is taken from a correspondingly sized rectangular block at a specified location in the source buffer
[DllImport("kernel32.dll", EntryPoint = "WriteConsoleOutputW", CharSet = CharSet.Unicode, SetLastError = true)]
internal static extern bool WriteConsoleOutput(
IntPtr hConsoleOutput,
// This pointer is treated as the origin of a two-dimensional array of CHAR_INFO structures whose size is specified by the dwBufferSize parameter.
[MarshalAs(UnmanagedType.LPArray), In] CHAR_INFO[,] lpBuffer,
COORD dwBufferSize,
COORD dwBufferCoord,
ref SMALL_RECT lpWriteRegion);
// Moves a block of data in a screen buffer. The effects of the move can be limited by specifying a clipping rectangle, so the contents of the console screen buffer outside the clipping rectangle are unchanged.
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool ScrollConsoleScreenBuffer(
IntPtr hConsoleOutput,
[In] ref SMALL_RECT lpScrollRectangle,
[In] ref SMALL_RECT lpClipRectangle,
COORD dwDestinationOrigin,
[In] ref CHAR_INFO lpFill);
[DllImport("kernel32.dll", SetLastError = true)]
static extern IntPtr GetStdHandle(int nStdHandle);
#endif
public override ConsoleColor BackgroundColor
{
get
{
#if noConsole
return GUIBackgroundColor;
#else
return Console.BackgroundColor;
#endif
}
set
{
#if noConsole
GUIBackgroundColor = value;
#else
Console.BackgroundColor = value;
#endif
}
}
public override System.Management.Automation.Host.Size BufferSize
{
get
{
#if !noConsole
if (Console.IsOutputRedirected)
// return default value for redirection. If no valid value is returned WriteLine will not be called
return new System.Management.Automation.Host.Size(120, 50);
else
return new System.Management.Automation.Host.Size(Console.BufferWidth, Console.BufferHeight);
#else
// return default value for Winforms. If no valid value is returned WriteLine will not be called
return new System.Management.Automation.Host.Size(120, 50);
#endif
}
set
{
#if !noConsole
Console.BufferWidth = value.Width;
Console.BufferHeight = value.Height;
#endif
}
}
public override Coordinates CursorPosition
{
get
{
#if !noConsole
return new Coordinates(Console.CursorLeft, Console.CursorTop);
#else
// Return dummy value for Winforms.
return new Coordinates(0, 0);
#endif
}
set
{
#if !noConsole
Console.CursorTop = value.Y;
Console.CursorLeft = value.X;
#endif
}
}
public override int CursorSize
{
get
{
#if !noConsole
return Console.CursorSize;
#else
// Return dummy value for Winforms.
return 25;
#endif
}
set
{
#if !noConsole
Console.CursorSize = value;
#endif
}
}
#if noConsole
private Form Invisible_Form = null;
#endif
public override void FlushInputBuffer()
{
#if !noConsole
if (!Console.IsInputRedirected)
{ while (Console.KeyAvailable) Console.ReadKey(true); }
#else
if (Invisible_Form != null)
{
Invisible_Form.Close();
Invisible_Form = null;
}
else
{
Invisible_Form = new Form();
Invisible_Form.Opacity = 0;
Invisible_Form.ShowInTaskbar = false;
Invisible_Form.Visible = true;
}
#endif
}
public override ConsoleColor ForegroundColor
{
get
{
#if noConsole
return GUIForegroundColor;
#else
return Console.ForegroundColor;
#endif
}
set
{
#if noConsole
GUIForegroundColor = value;
#else
Console.ForegroundColor = value;
#endif
}
}
public override BufferCell[,] GetBufferContents(System.Management.Automation.Host.Rectangle rectangle)
{
#if !noConsole
IntPtr hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
CHAR_INFO[,] buffer = new CHAR_INFO[rectangle.Bottom - rectangle.Top + 1, rectangle.Right - rectangle.Left + 1];
COORD buffer_size = new COORD() {X = (short)(rectangle.Right - rectangle.Left + 1), Y = (short)(rectangle.Bottom - rectangle.Top + 1)};
COORD buffer_index = new COORD() {X = 0, Y = 0};
SMALL_RECT screen_rect = new SMALL_RECT() {Left = (short)rectangle.Left, Top = (short)rectangle.Top, Right = (short)rectangle.Right, Bottom = (short)rectangle.Bottom};
ReadConsoleOutput(hStdOut, buffer, buffer_size, buffer_index, ref screen_rect);
System.Management.Automation.Host.BufferCell[,] ScreenBuffer = new System.Management.Automation.Host.BufferCell[rectangle.Bottom - rectangle.Top + 1, rectangle.Right - rectangle.Left + 1];
for (int y = 0; y <= rectangle.Bottom - rectangle.Top; y++)
for (int x = 0; x <= rectangle.Right - rectangle.Left; x++)
{
ScreenBuffer[y,x] = new System.Management.Automation.Host.BufferCell(buffer[y,x].AsciiChar, (System.ConsoleColor)(buffer[y,x].Attributes & 0xF), (System.ConsoleColor)((buffer[y,x].Attributes & 0xF0) / 0x10), System.Management.Automation.Host.BufferCellType.Complete);
}
return ScreenBuffer;
#else
System.Management.Automation.Host.BufferCell[,] ScreenBuffer = new System.Management.Automation.Host.BufferCell[rectangle.Bottom - rectangle.Top + 1, rectangle.Right - rectangle.Left + 1];
for (int y = 0; y <= rectangle.Bottom - rectangle.Top; y++)
for (int x = 0; x <= rectangle.Right - rectangle.Left; x++)
{
ScreenBuffer[y,x] = new System.Management.Automation.Host.BufferCell(' ', GUIForegroundColor, GUIBackgroundColor, System.Management.Automation.Host.BufferCellType.Complete);
}
return ScreenBuffer;
#endif
}
public override bool KeyAvailable
{
get
{
#if !noConsole
return Console.KeyAvailable;
#else
return true;
#endif
}
}
public override System.Management.Automation.Host.Size MaxPhysicalWindowSize
{
get
{
#if !noConsole
return new System.Management.Automation.Host.Size(Console.LargestWindowWidth, Console.LargestWindowHeight);
#else
// Dummy value for Winforms
return new System.Management.Automation.Host.Size(240, 84);
#endif
}
}
public override System.Management.Automation.Host.Size MaxWindowSize
{
get
{
#if !noConsole
return new System.Management.Automation.Host.Size(Console.BufferWidth, Console.BufferWidth);
#else
// Dummy value for Winforms
return new System.Management.Automation.Host.Size(120, 84);
#endif
}
}
public override KeyInfo ReadKey(ReadKeyOptions options)
{
#if !noConsole
ConsoleKeyInfo cki = Console.ReadKey((options & ReadKeyOptions.NoEcho)!=0);
ControlKeyStates cks = 0;
if ((cki.Modifiers & ConsoleModifiers.Alt) != 0)
cks |= ControlKeyStates.LeftAltPressed | ControlKeyStates.RightAltPressed;
if ((cki.Modifiers & ConsoleModifiers.Control) != 0)
cks |= ControlKeyStates.LeftCtrlPressed | ControlKeyStates.RightCtrlPressed;
if ((cki.Modifiers & ConsoleModifiers.Shift) != 0)
cks |= ControlKeyStates.ShiftPressed;
if (Console.CapsLock)
cks |= ControlKeyStates.CapsLockOn;
if (Console.NumberLock)
cks |= ControlKeyStates.NumLockOn;
return new KeyInfo((int)cki.Key, cki.KeyChar, cks, (options & ReadKeyOptions.IncludeKeyDown)!=0);
#else
if ((options & ReadKeyOptions.IncludeKeyDown)!=0)
return ReadKey_Box.Show(WindowTitle, "", true);
else
return ReadKey_Box.Show(WindowTitle, "", false);
#endif
}
public override void ScrollBufferContents(System.Management.Automation.Host.Rectangle source, Coordinates destination, System.Management.Automation.Host.Rectangle clip, BufferCell fill)
{ // no destination block clipping implemented
#if !noConsole
// clip area out of source range?
if ((source.Left > clip.Right) || (source.Right < clip.Left) || (source.Top > clip.Bottom) || (source.Bottom < clip.Top))
{ // clipping out of range -> nothing to do
return;
}
IntPtr hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
SMALL_RECT lpScrollRectangle = new SMALL_RECT() {Left = (short)source.Left, Top = (short)source.Top, Right = (short)(source.Right), Bottom = (short)(source.Bottom)};
SMALL_RECT lpClipRectangle;
if (clip != null)
{ lpClipRectangle = new SMALL_RECT() {Left = (short)clip.Left, Top = (short)clip.Top, Right = (short)(clip.Right), Bottom = (short)(clip.Bottom)}; }
else
{ lpClipRectangle = new SMALL_RECT() {Left = (short)0, Top = (short)0, Right = (short)(Console.WindowWidth - 1), Bottom = (short)(Console.WindowHeight - 1)}; }
COORD dwDestinationOrigin = new COORD() {X = (short)(destination.X), Y = (short)(destination.Y)};
CHAR_INFO lpFill = new CHAR_INFO() { AsciiChar = fill.Character, Attributes = (ushort)((int)(fill.ForegroundColor) + (int)(fill.BackgroundColor)*16) };
ScrollConsoleScreenBuffer(hStdOut, ref lpScrollRectangle, ref lpClipRectangle, dwDestinationOrigin, ref lpFill);
#endif
}
public override void SetBufferContents(System.Management.Automation.Host.Rectangle rectangle, BufferCell fill)
{
#if !noConsole
// using a trick: move the buffer out of the screen, the source area gets filled with the char fill.Character
if (rectangle.Left >= 0)
Console.MoveBufferArea(rectangle.Left, rectangle.Top, rectangle.Right-rectangle.Left+1, rectangle.Bottom-rectangle.Top+1, BufferSize.Width, BufferSize.Height, fill.Character, fill.ForegroundColor, fill.BackgroundColor);
else
{ // Clear-Host: move all content off the screen
Console.MoveBufferArea(0, 0, BufferSize.Width, BufferSize.Height, BufferSize.Width, BufferSize.Height, fill.Character, fill.ForegroundColor, fill.BackgroundColor);
}
#endif
}
public override void SetBufferContents(Coordinates origin, BufferCell[,] contents)
{
#if !noConsole
IntPtr hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
CHAR_INFO[,] buffer = new CHAR_INFO[contents.GetLength(0), contents.GetLength(1)];
COORD buffer_size = new COORD() {X = (short)(contents.GetLength(1)), Y = (short)(contents.GetLength(0))};
COORD buffer_index = new COORD() {X = 0, Y = 0};
SMALL_RECT screen_rect = new SMALL_RECT() {Left = (short)origin.X, Top = (short)origin.Y, Right = (short)(origin.X + contents.GetLength(1) - 1), Bottom = (short)(origin.Y + contents.GetLength(0) - 1)};
for (int y = 0; y < contents.GetLength(0); y++)
for (int x = 0; x < contents.GetLength(1); x++)
{
buffer[y,x] = new CHAR_INFO() { AsciiChar = contents[y,x].Character, Attributes = (ushort)((int)(contents[y,x].ForegroundColor) + (int)(contents[y,x].BackgroundColor)*16) };
}
WriteConsoleOutput(hStdOut, buffer, buffer_size, buffer_index, ref screen_rect);
#endif
}
public override Coordinates WindowPosition
{
get
{
Coordinates s = new Coordinates();
#if !noConsole
s.X = Console.WindowLeft;
s.Y = Console.WindowTop;
#else
// Dummy value for Winforms
s.X = 0;
s.Y = 0;
#endif
return s;
}
set
{
#if !noConsole
Console.WindowLeft = value.X;
Console.WindowTop = value.Y;
#endif
}
}
public override System.Management.Automation.Host.Size WindowSize
{
get
{
System.Management.Automation.Host.Size s = new System.Management.Automation.Host.Size();
#if !noConsole
s.Height = Console.WindowHeight;
s.Width = Console.WindowWidth;
#else
// Dummy value for Winforms
s.Height = 50;
s.Width = 120;
#endif
return s;
}
set
{
#if !noConsole
Console.WindowWidth = value.Width;
Console.WindowHeight = value.Height;
#endif
}
}
public override string WindowTitle
{
get
{
#if !noConsole
return Console.Title;
#else
return GUITitle;
#endif
}
set
{
#if !noConsole
Console.Title = value;
#else
GUITitle = value;
#endif
}
}
}
#if noConsole
public class Input_Box
{
[DllImport("user32.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)]
private static extern IntPtr MB_GetString(uint strId);
public static DialogResult Show(string strTitle, string strPrompt, ref string strVal, bool blSecure)
{
// Generate controls
Form form = new Form();
form.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
form.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
Label label = new Label();
TextBox textBox = new TextBox();
Button buttonOk = new Button();
Button buttonCancel = new Button();
// Sizes and positions are defined according to the label
// This control has to be finished first
if (string.IsNullOrEmpty(strPrompt))
{
if (blSecure)
label.Text = "Secure input: ";
else
label.Text = "Input: ";
}
else
label.Text = strPrompt;
label.Location = new Point(9, 19);
label.MaximumSize = new System.Drawing.Size(System.Windows.Forms.Screen.FromControl(form).Bounds.Width*5/8 - 18, 0);
label.AutoSize = true;
// Size of the label is defined not before Add()
form.Controls.Add(label);
// Generate textbox
if (blSecure) textBox.UseSystemPasswordChar = true;
textBox.Text = strVal;
textBox.SetBounds(12, label.Bottom, label.Right - 12, 20);
// Generate buttons
// get localized "OK"-string
string sTextOK = Marshal.PtrToStringUni(MB_GetString(0));
if (string.IsNullOrEmpty(sTextOK))
buttonOk.Text = "OK";
else
buttonOk.Text = sTextOK;
// get localized "Cancel"-string
string sTextCancel = Marshal.PtrToStringUni(MB_GetString(1));
if (string.IsNullOrEmpty(sTextCancel))
buttonCancel.Text = "Cancel";
else
buttonCancel.Text = sTextCancel;
buttonOk.DialogResult = DialogResult.OK;
buttonCancel.DialogResult = DialogResult.Cancel;
buttonOk.SetBounds(System.Math.Max(12, label.Right - 158), label.Bottom + 36, 75, 23);
buttonCancel.SetBounds(System.Math.Max(93, label.Right - 77), label.Bottom + 36, 75, 23);
// Configure form
form.Text = strTitle;
form.ClientSize = new System.Drawing.Size(System.Math.Max(178, label.Right + 10), label.Bottom + 71);
form.Controls.AddRange(new Control[] { textBox, buttonOk, buttonCancel });
form.FormBorderStyle = FormBorderStyle.FixedDialog;
form.StartPosition = FormStartPosition.CenterScreen;
try {
form.Icon = Icon.ExtractAssociatedIcon(Assembly.GetExecutingAssembly().Location);
}
catch { }
form.MinimizeBox = false;
form.MaximizeBox = false;
form.AcceptButton = buttonOk;
form.CancelButton = buttonCancel;
// Show form and compute results
DialogResult dialogResult = form.ShowDialog();
strVal = textBox.Text;
return dialogResult;
}
public static DialogResult Show(string strTitle, string strPrompt, ref string strVal)
{
return Show(strTitle, strPrompt, ref strVal, false);
}
}
public class Choice_Box
{
public static int Show(System.Collections.ObjectModel.Collection<ChoiceDescription> arrChoice, int intDefault, string strTitle, string strPrompt)
{
// cancel if array is empty
if (arrChoice == null) return -1;
if (arrChoice.Count < 1) return -1;
// Generate controls
Form form = new Form();
form.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
form.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
RadioButton[] aradioButton = new RadioButton[arrChoice.Count];
ToolTip toolTip = new ToolTip();
Button buttonOk = new Button();
// Sizes and positions are defined according to the label
// This control has to be finished first when a prompt is available
int iPosY = 19, iMaxX = 0;
if (!string.IsNullOrEmpty(strPrompt))
{
Label label = new Label();
label.Text = strPrompt;
label.Location = new Point(9, 19);
label.MaximumSize = new System.Drawing.Size(System.Windows.Forms.Screen.FromControl(form).Bounds.Width*5/8 - 18, 0);
label.AutoSize = true;
// The size of the label is only determined using the Add() method
form.Controls.Add(label);
iPosY = label.Bottom;
iMaxX = label.Right;
}
// The other sizes and positions are based on the radio buttons
// Now let's finish these controls
int Counter = 0;
int tempWidth = System.Windows.Forms.Screen.FromControl(form).Bounds.Width*5/8 - 18;
foreach (ChoiceDescription sAuswahl in arrChoice)
{
aradioButton[Counter] = new RadioButton();
aradioButton[Counter].Text = sAuswahl.Label;
if (Counter == intDefault) aradioButton[Counter].Checked = true;
aradioButton[Counter].Location = new Point(9, iPosY);
aradioButton[Counter].AutoSize = true;
// The size of the label is only determined using the Add() method
form.Controls.Add(aradioButton[Counter]);
if (aradioButton[Counter].Width > tempWidth)
{ // radio field to wide for screen -> make two lines
int tempHeight = aradioButton[Counter].Height;
aradioButton[Counter].Height = tempHeight*(1 + (aradioButton[Counter].Width-1)/tempWidth);
aradioButton[Counter].Width = tempWidth;
aradioButton[Counter].AutoSize = false;
}
iPosY = aradioButton[Counter].Bottom;
if (aradioButton[Counter].Right > iMaxX) { iMaxX = aradioButton[Counter].Right; }
if (!string.IsNullOrEmpty(sAuswahl.HelpMessage))
toolTip.SetToolTip(aradioButton[Counter], sAuswahl.HelpMessage);
Counter++;
}
// Show tooltip even when parent window is inactive
toolTip.ShowAlways = true;
// Create button
buttonOk.Text = "OK";
buttonOk.DialogResult = DialogResult.OK;
buttonOk.SetBounds(System.Math.Max(12, iMaxX - 77), iPosY + 36, 75, 23);
// configure form
if (string.IsNullOrEmpty(strTitle))
form.Text = System.AppDomain.CurrentDomain.FriendlyName;
else
form.Text = strTitle;
form.ClientSize = new System.Drawing.Size(System.Math.Max(178, iMaxX + 10), iPosY + 71);
form.Controls.Add(buttonOk);
form.FormBorderStyle = FormBorderStyle.FixedDialog;
form.StartPosition = FormStartPosition.CenterScreen;
try {
form.Icon = Icon.ExtractAssociatedIcon(Assembly.GetExecutingAssembly().Location);
}
catch { }
form.MinimizeBox = false;
form.MaximizeBox = false;
form.AcceptButton = buttonOk;
// show and compute form
if (form.ShowDialog() == DialogResult.OK)
{ int iRueck = -1;
for (Counter = 0; Counter < arrChoice.Count; Counter++)
{
if (aradioButton[Counter].Checked == true) { iRueck = Counter; }
}
return iRueck;
}
else
return -1;
}
}
public class ReadKey_Box
{
[DllImport("user32.dll")]
public static extern int ToUnicode(uint wVirtKey, uint wScanCode, byte[] lpKeyState,
[Out, MarshalAs(UnmanagedType.LPWStr, SizeConst = 64)] System.Text.StringBuilder pwszBuff, int cchBuff, uint wFlags);
static string GetCharFromKeys(Keys keys, bool blShift, bool blAltGr)
{
System.Text.StringBuilder buffer = new System.Text.StringBuilder(64);
byte[] keyboardState = new byte[256];
if (blShift)
{ keyboardState[(int) Keys.ShiftKey] = 0xff; }
if (blAltGr)
{ keyboardState[(int) Keys.ControlKey] = 0xff;
keyboardState[(int) Keys.Menu] = 0xff;
}
if (ToUnicode((uint) keys, 0, keyboardState, buffer, 64, 0) >= 1)
return buffer.ToString();
else
return "\0";
}
class Keyboard_Form : Form
{
public Keyboard_Form()
{
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.KeyDown += new KeyEventHandler(Keyboard_Form_KeyDown);
this.KeyUp += new KeyEventHandler(Keyboard_Form_KeyUp);
}
// check for KeyDown or KeyUp?
public bool checkKeyDown = true;
// key code for pressed key
public KeyInfo keyinfo;
void Keyboard_Form_KeyDown(object sender, KeyEventArgs e)
{
if (checkKeyDown)
{ // store key info
keyinfo.VirtualKeyCode = e.KeyValue;
keyinfo.Character = GetCharFromKeys(e.KeyCode, e.Shift, e.Alt & e.Control)[0];
keyinfo.KeyDown = false;
keyinfo.ControlKeyState = 0;
if (e.Alt) { keyinfo.ControlKeyState = ControlKeyStates.LeftAltPressed | ControlKeyStates.RightAltPressed; }
if (e.Control)
{ keyinfo.ControlKeyState |= ControlKeyStates.LeftCtrlPressed | ControlKeyStates.RightCtrlPressed;
if (!e.Alt) { if (e.KeyValue > 64 && e.KeyValue < 96) keyinfo.Character = (char)(e.KeyValue - 64); }
}
if (e.Shift) { keyinfo.ControlKeyState |= ControlKeyStates.ShiftPressed; }
if ((e.Modifiers & System.Windows.Forms.Keys.CapsLock) > 0) { keyinfo.ControlKeyState |= ControlKeyStates.CapsLockOn; }
if ((e.Modifiers & System.Windows.Forms.Keys.NumLock) > 0) { keyinfo.ControlKeyState |= ControlKeyStates.NumLockOn; }
// and close the form
this.Close();
}
}
void Keyboard_Form_KeyUp(object sender, KeyEventArgs e)
{
if (!checkKeyDown)
{ // store key info
keyinfo.VirtualKeyCode = e.KeyValue;
keyinfo.Character = GetCharFromKeys(e.KeyCode, e.Shift, e.Alt & e.Control)[0];
keyinfo.KeyDown = true;
keyinfo.ControlKeyState = 0;
if (e.Alt) { keyinfo.ControlKeyState = ControlKeyStates.LeftAltPressed | ControlKeyStates.RightAltPressed; }
if (e.Control)
{ keyinfo.ControlKeyState |= ControlKeyStates.LeftCtrlPressed | ControlKeyStates.RightCtrlPressed;
if (!e.Alt)
{ if (e.KeyValue > 64 && e.KeyValue < 96) keyinfo.Character = (char)(e.KeyValue - 64); }
}
if (e.Shift) { keyinfo.ControlKeyState |= ControlKeyStates.ShiftPressed; }
if ((e.Modifiers & System.Windows.Forms.Keys.CapsLock) > 0) { keyinfo.ControlKeyState |= ControlKeyStates.CapsLockOn; }
if ((e.Modifiers & System.Windows.Forms.Keys.NumLock) > 0) { keyinfo.ControlKeyState |= ControlKeyStates.NumLockOn; }
// and close the form
this.Close();
}
}
}
public static KeyInfo Show(string strTitle, string strPrompt, bool blIncludeKeyDown)
{
// Create controls
Keyboard_Form form = new Keyboard_Form();
Label label = new Label();
// The label determines the sizes and positions
// So finish this control first
if (string.IsNullOrEmpty(strPrompt))
label.Text = "Press a key";
else
label.Text = strPrompt;
label.Location = new Point(9, 19);
label.MaximumSize = new System.Drawing.Size(System.Windows.Forms.Screen.FromControl(form).Bounds.Width*5/8 - 18, 0);
label.AutoSize = true;
// The size of the label is only determined using the Add() method
form.Controls.Add(label);
// configure form
form.Text = strTitle;
form.ClientSize = new System.Drawing.Size(System.Math.Max(178, label.Right + 10), label.Bottom + 55);
form.FormBorderStyle = FormBorderStyle.FixedDialog;
form.StartPosition = FormStartPosition.CenterScreen;
try {
form.Icon = Icon.ExtractAssociatedIcon(Assembly.GetExecutingAssembly().Location);
}
catch { }
form.MinimizeBox = false;
form.MaximizeBox = false;
// show and compute form
form.checkKeyDown = blIncludeKeyDown;
form.ShowDialog();
return form.keyinfo;
}
}
public class Progress_Form : Form
{
private ConsoleColor ProgressBarColor = ConsoleColor.DarkCyan;
private string WindowTitle = "";
#if !noVisualStyles
private System.Timers.Timer timer = new System.Timers.Timer();
private int barNumber = -1;
private int barValue = -1;
private bool inTick = false;
#endif
struct Progress_Data
{
internal Label lbActivity;
internal Label lbStatus;
internal ProgressBar objProgressBar;
internal Label lbRemainingTime;
internal Label lbOperation;
internal int ActivityId;
internal int ParentActivityId;
internal int Depth;
};
private List<Progress_Data> progressDataList = new List<Progress_Data>();
private Color DrawingColor(ConsoleColor color)
{ // convert ConsoleColor to System.Drawing.Color
switch (color)
{
case ConsoleColor.Black: return Color.Black;
case ConsoleColor.Blue: return Color.Blue;
case ConsoleColor.Cyan: return Color.Cyan;
case ConsoleColor.DarkBlue: return ColorTranslator.FromHtml("#000080");
case ConsoleColor.DarkGray: return ColorTranslator.FromHtml("#808080");
case ConsoleColor.DarkGreen: return ColorTranslator.FromHtml("#008000");
case ConsoleColor.DarkCyan: return ColorTranslator.FromHtml("#008080");
case ConsoleColor.DarkMagenta: return ColorTranslator.FromHtml("#800080");
case ConsoleColor.DarkRed: return ColorTranslator.FromHtml("#800000");
case ConsoleColor.DarkYellow: return ColorTranslator.FromHtml("#808000");
case ConsoleColor.Gray: return ColorTranslator.FromHtml("#C0C0C0");
case ConsoleColor.Green: return ColorTranslator.FromHtml("#00FF00");
case ConsoleColor.Magenta: return Color.Magenta;
case ConsoleColor.Red: return Color.Red;
case ConsoleColor.White: return Color.White;
default: return Color.Yellow;
}
}
public Progress_Form()
{
InitializeComponent();
}
public Progress_Form(ConsoleColor BarColor)
{
ProgressBarColor = BarColor;
InitializeComponent();
}
public Progress_Form(string Title, ConsoleColor BarColor)
{
WindowTitle = Title;
ProgressBarColor = BarColor;
InitializeComponent();
}
private void InitializeComponent()
{
this.SuspendLayout();
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.AutoScroll = true;
this.Text = WindowTitle;
this.Height = 147;
this.Width = 800;
this.BackColor = Color.White;
this.FormBorderStyle = FormBorderStyle.FixedSingle;
this.MinimizeBox = false;
this.MaximizeBox = false;
this.ControlBox = false;
this.StartPosition = FormStartPosition.CenterScreen;
this.ResumeLayout();
#if !noVisualStyles
timer.Elapsed += new System.Timers.ElapsedEventHandler(TimeTick);
timer.Interval = 50; // milliseconds
timer.AutoReset = true;
timer.Start();
#endif
}
#if !noVisualStyles
private void TimeTick(object source, System.Timers.ElapsedEventArgs e)
{ // worker function that is called by timer event
if (inTick) return;
inTick = true;
if (barNumber >= 0)
{
if (barValue >= 0)
{
progressDataList[barNumber].objProgressBar.Value = barValue;
barValue = -1;
}
progressDataList[barNumber].objProgressBar.Refresh();
}
inTick = false;
}
#endif
private void AddBar(ref Progress_Data pd, int position)
{
// Create Label
pd.lbActivity = new Label();
pd.lbActivity.Left = 5;
pd.lbActivity.Top = 104*position + 10;
pd.lbActivity.Width = 800 - 20;
pd.lbActivity.Height = 16;
pd.lbActivity.Font = new Font(pd.lbActivity.Font, FontStyle.Bold);
pd.lbActivity.Text = "";
// Add Label to Form
this.Controls.Add(pd.lbActivity);
// Create Label
pd.lbStatus = new Label();
pd.lbStatus.Left = 25;
pd.lbStatus.Top = 104*position + 26;
pd.lbStatus.Width = 800 - 40;
pd.lbStatus.Height = 16;
pd.lbStatus.Text = "";
// Add Label to Form
this.Controls.Add(pd.lbStatus);
// Create ProgressBar
pd.objProgressBar = new ProgressBar();
pd.objProgressBar.Value = 0;
#if !noVisualStyles // was not !
pd.objProgressBar.Style = ProgressBarStyle.Continuous;
#else
pd.objProgressBar.Style = ProgressBarStyle.Blocks;
#endif
pd.objProgressBar.ForeColor = DrawingColor(ProgressBarColor);
if (pd.Depth < 15)
{
pd.objProgressBar.Size = new System.Drawing.Size(800 - 60 - 30*pd.Depth, 20);
pd.objProgressBar.Left = 25 + 30*pd.Depth;
}
else
{
pd.objProgressBar.Size = new System.Drawing.Size(800 - 60 - 450, 20);
pd.objProgressBar.Left = 25 + 450;
}
pd.objProgressBar.Top = 104*position + 47;
// Add ProgressBar to Form
this.Controls.Add(pd.objProgressBar);
// Create Label
pd.lbRemainingTime = new Label();
pd.lbRemainingTime.Left = 5;
pd.lbRemainingTime.Top = 104*position + 72;
pd.lbRemainingTime.Width = 800 - 20;
pd.lbRemainingTime.Height = 16;
pd.lbRemainingTime.Text = "";
// Add Label to Form
this.Controls.Add(pd.lbRemainingTime);
// Create Label
pd.lbOperation = new Label();
pd.lbOperation.Left = 25;
pd.lbOperation.Top = 104*position + 88;
pd.lbOperation.Width = 800 - 40;
pd.lbOperation.Height = 16;
pd.lbOperation.Text = "";
// Add Label to Form
this.Controls.Add(pd.lbOperation);
}
public int GetCount()
{
return progressDataList.Count;
}
public void Update(ProgressRecord objRecord)
{
if (objRecord == null) return;
int currentProgress = -1;
for (int i = 0; i < progressDataList.Count; i++)
{
if (progressDataList[i].ActivityId == objRecord.ActivityId)
{ currentProgress = i; break; }
}
if (objRecord.RecordType == ProgressRecordType.Completed)
{
if (currentProgress >= 0)
{
#if !noVisualStyles
if (barNumber == currentProgress) barNumber = -1;
#endif
this.Controls.Remove(progressDataList[currentProgress].lbActivity);
this.Controls.Remove(progressDataList[currentProgress].lbStatus);
this.Controls.Remove(progressDataList[currentProgress].objProgressBar);
this.Controls.Remove(progressDataList[currentProgress].lbRemainingTime);
this.Controls.Remove(progressDataList[currentProgress].lbOperation);
progressDataList[currentProgress].lbActivity.Dispose();
progressDataList[currentProgress].lbStatus.Dispose();
progressDataList[currentProgress].objProgressBar.Dispose();
progressDataList[currentProgress].lbRemainingTime.Dispose();
progressDataList[currentProgress].lbOperation.Dispose();
progressDataList.RemoveAt(currentProgress);
}
if (progressDataList.Count == 0)
{
#if !noVisualStyles
timer.Stop();
timer.Dispose();
#endif
this.Close();
return;
}
if (currentProgress < 0) return;
for (int i = currentProgress; i < progressDataList.Count; i++)
{
progressDataList[i].lbActivity.Top = 104*i + 10;
progressDataList[i].lbStatus.Top = 104*i + 26;
progressDataList[i].objProgressBar.Top = 104*i + 47;
progressDataList[i].lbRemainingTime.Top = 104*i + 72;
progressDataList[i].lbOperation.Top = 104*i + 88;
}
if (104*progressDataList.Count + 43 <= System.Windows.Forms.Screen.FromControl(this).Bounds.Height)
{
this.Height = 104*progressDataList.Count + 43;
this.Location = new Point((System.Windows.Forms.Screen.FromControl(this).Bounds.Width - this.Width)/2, (System.Windows.Forms.Screen.FromControl(this).Bounds.Height - this.Height)/2);
}
else
{
this.Height = System.Windows.Forms.Screen.FromControl(this).Bounds.Height;
this.Location = new Point((System.Windows.Forms.Screen.FromControl(this).Bounds.Width - this.Width)/2, 0);
}
return;
}
if (currentProgress < 0)
{
Progress_Data pd = new Progress_Data();
pd.ActivityId = objRecord.ActivityId;
pd.ParentActivityId = objRecord.ParentActivityId;
pd.Depth = 0;
int nextid = -1;
int parentid = -1;
if (pd.ParentActivityId >= 0)
{
for (int i = 0; i < progressDataList.Count; i++)
{
if (progressDataList[i].ActivityId == pd.ParentActivityId)
{ parentid = i; break; }
}
}
if (parentid >= 0)
{
pd.Depth = progressDataList[parentid].Depth + 1;
for (int i = parentid + 1; i < progressDataList.Count; i++)
{
if ((progressDataList[i].Depth < pd.Depth) || ((progressDataList[i].Depth == pd.Depth) && (progressDataList[i].ParentActivityId != pd.ParentActivityId)))
{ nextid = i;
break;
}
}
}
if (nextid == -1)
{
AddBar(ref pd, progressDataList.Count);
currentProgress = progressDataList.Count;
progressDataList.Add(pd);
}
else
{
AddBar(ref pd, nextid);
currentProgress = nextid;
progressDataList.Insert(nextid, pd);
for (int i = currentProgress+1; i < progressDataList.Count; i++)
{
progressDataList[i].lbActivity.Top = 104*i + 10;
progressDataList[i].lbStatus.Top = 104*i + 26;
progressDataList[i].objProgressBar.Top = 104*i + 47;
progressDataList[i].lbRemainingTime.Top = 104*i + 72;
progressDataList[i].lbOperation.Top = 104*i + 88;
}
}
if (104*progressDataList.Count + 43 <= System.Windows.Forms.Screen.FromControl(this).Bounds.Height)
{
this.Height = 104*progressDataList.Count + 43;
this.Location = new Point((System.Windows.Forms.Screen.FromControl(this).Bounds.Width - this.Width)/2, (System.Windows.Forms.Screen.FromControl(this).Bounds.Height - this.Height)/2);
}
else
{
this.Height = System.Windows.Forms.Screen.FromControl(this).Bounds.Height;
this.Location = new Point((System.Windows.Forms.Screen.FromControl(this).Bounds.Width - this.Width)/2, 0);
}
}
if (!string.IsNullOrEmpty(objRecord.Activity))
progressDataList[currentProgress].lbActivity.Text = objRecord.Activity;
else
progressDataList[currentProgress].lbActivity.Text = "";
if (!string.IsNullOrEmpty(objRecord.StatusDescription))
progressDataList[currentProgress].lbStatus.Text = objRecord.StatusDescription;
else
progressDataList[currentProgress].lbStatus.Text = "";
if ((objRecord.PercentComplete >= 0) && (objRecord.PercentComplete <= 100))
{
#if !noVisualStyles
if (objRecord.PercentComplete < 100)
progressDataList[currentProgress].objProgressBar.Value = objRecord.PercentComplete + 1;
else
progressDataList[currentProgress].objProgressBar.Value = 99;
progressDataList[currentProgress].objProgressBar.Visible = true;
barNumber = currentProgress;
barValue = objRecord.PercentComplete;
#else
progressDataList[currentProgress].objProgressBar.Value = objRecord.PercentComplete;
progressDataList[currentProgress].objProgressBar.Visible = true;
#endif
}
else
{ if (objRecord.PercentComplete > 100)
{
progressDataList[currentProgress].objProgressBar.Value = 0;
progressDataList[currentProgress].objProgressBar.Visible = true;
#if !noVisualStyles
barNumber = currentProgress;
barValue = 0;
#endif
}
else
{
progressDataList[currentProgress].objProgressBar.Visible = false;
#if !noVisualStyles
if (barNumber == currentProgress) barNumber = -1;
#endif
}
}
if (objRecord.SecondsRemaining >= 0)
{
System.TimeSpan objTimeSpan = new System.TimeSpan(0, 0, objRecord.SecondsRemaining);
progressDataList[currentProgress].lbRemainingTime.Text = "Remaining time: " + string.Format("{0:00}:{1:00}:{2:00}", (int)objTimeSpan.TotalHours, objTimeSpan.Minutes, objTimeSpan.Seconds);
}
else
progressDataList[currentProgress].lbRemainingTime.Text = "";
if (!string.IsNullOrEmpty(objRecord.CurrentOperation))
progressDataList[currentProgress].lbOperation.Text = objRecord.CurrentOperation;
else
progressDataList[currentProgress].lbOperation.Text = "";
Application.DoEvents();
}
}
#endif
public class MainModuleUI : PSHostUserInterface
{
private MainModuleRawUI rawUI = null;
public ConsoleColor ErrorForegroundColor = ConsoleColor.Red;
public ConsoleColor ErrorBackgroundColor = ConsoleColor.Black;
public ConsoleColor WarningForegroundColor = ConsoleColor.Yellow;
public ConsoleColor WarningBackgroundColor = ConsoleColor.Black;
public ConsoleColor DebugForegroundColor = ConsoleColor.Yellow;
public ConsoleColor DebugBackgroundColor = ConsoleColor.Black;
public ConsoleColor VerboseForegroundColor = ConsoleColor.Yellow;
public ConsoleColor VerboseBackgroundColor = ConsoleColor.Black;
#if !noConsole
public ConsoleColor ProgressForegroundColor = ConsoleColor.Yellow;
#else
public ConsoleColor ProgressForegroundColor = ConsoleColor.DarkCyan;
#endif
public ConsoleColor ProgressBackgroundColor = ConsoleColor.DarkCyan;
public MainModuleUI() : base()
{
rawUI = new MainModuleRawUI();
#if !noConsole
rawUI.ForegroundColor = Console.ForegroundColor;
rawUI.BackgroundColor = Console.BackgroundColor;
#endif
}
public override Dictionary<string, PSObject> Prompt(string caption, string message, System.Collections.ObjectModel.Collection<FieldDescription> descriptions)
{
#if !noConsole
if (!string.IsNullOrEmpty(caption)) WriteLine(caption);
if (!string.IsNullOrEmpty(message)) WriteLine(message);
#else
if ((!string.IsNullOrEmpty(caption)) || (!string.IsNullOrEmpty(message)))
{ string sTitel = System.AppDomain.CurrentDomain.FriendlyName, sMeldung = "";
if (!string.IsNullOrEmpty(caption)) sTitel = caption;
if (!string.IsNullOrEmpty(message)) sMeldung = message;
MessageBox.Show(sMeldung, sTitel);
}
// Reset label text for Input_Box
ib_message = "";
#endif
Dictionary<string, PSObject> ret = new Dictionary<string, PSObject>();
foreach (FieldDescription cd in descriptions)
{
Type t = null;
if (string.IsNullOrEmpty(cd.ParameterAssemblyFullName))
t = typeof(string);
else
t = Type.GetType(cd.ParameterAssemblyFullName);
if (t.IsArray)
{
Type elementType = t.GetElementType();
Type genericListType = Type.GetType("System.Collections.Generic.List"+((char)0x60).ToString()+"1");
genericListType = genericListType.MakeGenericType(new Type[] { elementType });
ConstructorInfo constructor = genericListType.GetConstructor(BindingFlags.CreateInstance | BindingFlags.Instance | BindingFlags.Public, null, Type.EmptyTypes, null);
object resultList = constructor.Invoke(null);
int index = 0;
string data = "";
do
{
try
{
#if !noConsole
if (!string.IsNullOrEmpty(cd.Name)) Write(string.Format("{0}[{1}]: ", cd.Name, index));
#else
if (!string.IsNullOrEmpty(cd.Name)) ib_message = string.Format("{0}[{1}]: ", cd.Name, index);
#endif
data = ReadLine();
if (string.IsNullOrEmpty(data)) break;
object o = System.Convert.ChangeType(data, elementType);
genericListType.InvokeMember("Add", BindingFlags.InvokeMethod | BindingFlags.Public | BindingFlags.Instance, null, resultList, new object[] { o });
}
catch (Exception e)
{
throw e;
}
index++;
} while (true);
System.Array retArray = (System.Array )genericListType.InvokeMember("ToArray", BindingFlags.InvokeMethod | BindingFlags.Public | BindingFlags.Instance, null, resultList, null);
ret.Add(cd.Name, new PSObject(retArray));
}
else
{
object o = null;
string l = null;
try
{
if (t != typeof(System.Security.SecureString))
{
if (t != typeof(System.Management.Automation.PSCredential))
{
#if !noConsole
if (!string.IsNullOrEmpty(cd.Name)) Write(cd.Name);
if (!string.IsNullOrEmpty(cd.HelpMessage)) Write(" (Type !? for help.)");
if ((!string.IsNullOrEmpty(cd.Name)) || (!string.IsNullOrEmpty(cd.HelpMessage))) Write(": ");
#else
if (!string.IsNullOrEmpty(cd.Name)) ib_message = string.Format("{0}: ", cd.Name);
if (!string.IsNullOrEmpty(cd.HelpMessage)) ib_message += "\n(Type !? for help.)";
#endif
do {
l = ReadLine();
if (l == "!?")
WriteLine(cd.HelpMessage);
else
{
if (string.IsNullOrEmpty(l)) o = cd.DefaultValue;
if (o == null)
{
try {
o = System.Convert.ChangeType(l, t);
}
catch {
Write("Wrong format, please repeat input: ");
l = "!?";
}
}
}
} while (l == "!?");
}
else
{
PSCredential pscred = PromptForCredential("", "", "", "");
o = pscred;
}
}
else
{
#if !noConsole
if (!string.IsNullOrEmpty(cd.Name)) Write(string.Format("{0}: ", cd.Name));
#else
if (!string.IsNullOrEmpty(cd.Name)) ib_message = string.Format("{0}: ", cd.Name);
#endif
SecureString pwd = null;
pwd = ReadLineAsSecureString();
o = pwd;
}
ret.Add(cd.Name, new PSObject(o));
}
catch (Exception e)
{
throw e;
}
}
}
#if noConsole
// Reset label text for input box
ib_message = "";
#endif
return ret;
}
public override int PromptForChoice(string caption, string message, System.Collections.ObjectModel.Collection<ChoiceDescription> choices, int defaultChoice)
{
#if noConsole
int iReturn = Choice_Box.Show(choices, defaultChoice, caption, message);
if (iReturn == -1) { iReturn = defaultChoice; }
return iReturn;
#else
if (!string.IsNullOrEmpty(caption)) WriteLine(caption);
WriteLine(message);
do {
int idx = 0;
SortedList<string, int> res = new SortedList<string, int>();
string defkey = "";
foreach (ChoiceDescription cd in choices)
{
string lkey = cd.Label.Substring(0, 1), ltext = cd.Label;
int pos = cd.Label.IndexOf('&');
if (pos > -1)
{
lkey = cd.Label.Substring(pos + 1, 1).ToUpper();
if (pos > 0)
ltext = cd.Label.Substring(0, pos) + cd.Label.Substring(pos + 1);
else
ltext = cd.Label.Substring(1);
}
res.Add(lkey.ToLower(), idx);
if (idx > 0) Write(" ");
if (idx == defaultChoice)
{
Write(VerboseForegroundColor, rawUI.BackgroundColor, string.Format("[{0}] {1}", lkey, ltext));
defkey = lkey;
}
else
Write(rawUI.ForegroundColor, rawUI.BackgroundColor, string.Format("[{0}] {1}", lkey, ltext));
idx++;
}
Write(rawUI.ForegroundColor, rawUI.BackgroundColor, string.Format(" [?] Help (default is \"{0}\"): ", defkey));
string inpkey = "";
try
{
inpkey = Console.ReadLine().ToLower();
if (res.ContainsKey(inpkey)) return res[inpkey];
if (string.IsNullOrEmpty(inpkey)) return defaultChoice;
}
catch { }
if (inpkey == "?")
{
foreach (ChoiceDescription cd in choices)
{
string lkey = cd.Label.Substring(0, 1);
int pos = cd.Label.IndexOf('&');
if (pos > -1) lkey = cd.Label.Substring(pos + 1, 1).ToUpper();
if (!string.IsNullOrEmpty(cd.HelpMessage))
WriteLine(rawUI.ForegroundColor, rawUI.BackgroundColor, string.Format("{0} - {1}", lkey, cd.HelpMessage));
else
WriteLine(rawUI.ForegroundColor, rawUI.BackgroundColor, string.Format("{0} -", lkey));
}
}
} while (true);
#endif
}
public override PSCredential PromptForCredential(string caption, string message, string userName, string targetName, PSCredentialTypes allowedCredentialTypes, PSCredentialUIOptions options)
{
#if !noConsole && !credentialGUI
if (!string.IsNullOrEmpty(caption)) WriteLine(caption);
WriteLine(message);
string un;
if ((string.IsNullOrEmpty(userName)) || ((options & PSCredentialUIOptions.ReadOnlyUserName) == 0))
{
Write("User name: ");
un = ReadLine();
}
else
{
Write("User name: ");
if (!string.IsNullOrEmpty(targetName)) Write(targetName + "\\");
WriteLine(userName);
un = userName;
}
SecureString pwd = null;
Write("Password: ");
pwd = ReadLineAsSecureString();
if (string.IsNullOrEmpty(un)) un = "<NOUSER>";
if (!string.IsNullOrEmpty(targetName))
{
if (un.IndexOf('\\') < 0) un = targetName + "\\" + un;
}
PSCredential c2 = new PSCredential(un, pwd);
return c2;
#else
Credential_Form.User_Pwd cred = Credential_Form.PromptForPassword(caption, message, targetName, userName, allowedCredentialTypes, options);
if (cred != null)
{
System.Security.SecureString x = new System.Security.SecureString();
foreach (char c in cred.Password.ToCharArray())
x.AppendChar(c);
return new PSCredential(cred.User, x);
}
return null;
#endif
}
public override PSCredential PromptForCredential(string caption, string message, string userName, string targetName)
{
#if !noConsole && !credentialGUI
if (!string.IsNullOrEmpty(caption)) WriteLine(caption);
WriteLine(message);
string un;
if (string.IsNullOrEmpty(userName))
{
Write("User name: ");
un = ReadLine();
}
else
{
Write("User name: ");
if (!string.IsNullOrEmpty(targetName)) Write(targetName + "\\");
WriteLine(userName);
un = userName;
}
SecureString pwd = null;
Write("Password: ");
pwd = ReadLineAsSecureString();
if (string.IsNullOrEmpty(un)) un = "<NOUSER>";
if (!string.IsNullOrEmpty(targetName))
{
if (un.IndexOf('\\') < 0) un = targetName + "\\" + un;
}
PSCredential c2 = new PSCredential(un, pwd);
return c2;
#else
Credential_Form.User_Pwd cred = Credential_Form.PromptForPassword(caption, message, targetName, userName, PSCredentialTypes.Default, PSCredentialUIOptions.Default);
if (cred != null)
{
System.Security.SecureString x = new System.Security.SecureString();
foreach (char c in cred.Password.ToCharArray())
x.AppendChar(c);
return new PSCredential(cred.User, x);
}
return null;
#endif
}
public override PSHostRawUserInterface RawUI
{
get { return rawUI; }
}
#if noConsole
private string ib_message;
#endif
public override string ReadLine()
{
#if noConsole
string sWert = "";
if (Input_Box.Show(rawUI.WindowTitle, ib_message, ref sWert) == DialogResult.OK)
return sWert;
else
#if exitOnCancel
Environment.Exit(1);
return "";
#else
return "";
#endif
#else
return Console.ReadLine();
#endif
}
private System.Security.SecureString getPassword()
{
System.Security.SecureString pwd = new System.Security.SecureString();
while (true)
{
ConsoleKeyInfo i = Console.ReadKey(true);
if (i.Key == ConsoleKey.Enter)
{
Console.WriteLine();
break;
}
else if (i.Key == ConsoleKey.Backspace)
{
if (pwd.Length > 0)
{
pwd.RemoveAt(pwd.Length - 1);
Console.Write("\b \b");
}
}
else if (i.KeyChar != '\u0000')
{
pwd.AppendChar(i.KeyChar);
Console.Write("*");
}
}
return pwd;
}
public override System.Security.SecureString ReadLineAsSecureString()
{
System.Security.SecureString secstr = new System.Security.SecureString();
#if noConsole
string sWert = "";
if (Input_Box.Show(rawUI.WindowTitle, ib_message, ref sWert, true) == DialogResult.OK)
{
foreach (char ch in sWert)
secstr.AppendChar(ch);
}
#if exitOnCancel
else
Environment.Exit(1);
#endif
#else
secstr = getPassword();
#endif
return secstr;
}
// called by Write-Host
public override void Write(ConsoleColor foregroundColor, ConsoleColor backgroundColor, string value)
{
#if !noOutput
#if noConsole
if ((!string.IsNullOrEmpty(value)) && (value != "\n"))
MessageBox.Show(value, rawUI.WindowTitle);
#else
ConsoleColor fgc = Console.ForegroundColor, bgc = Console.BackgroundColor;
Console.ForegroundColor = foregroundColor;
Console.BackgroundColor = backgroundColor;
Console.Write(value);
Console.ForegroundColor = fgc;
Console.BackgroundColor = bgc;
#endif
#endif
}
public override void Write(string value)
{
#if !noOutput
#if noConsole
if ((!string.IsNullOrEmpty(value)) && (value != "\n"))
MessageBox.Show(value, rawUI.WindowTitle);
#else
Console.Write(value);
#endif
#endif
}
// called by Write-Debug
public override void WriteDebugLine(string message)
{
#if !noError
#if noConsole
MessageBox.Show(message, rawUI.WindowTitle, MessageBoxButtons.OK, MessageBoxIcon.Information);
#else
WriteLineInternal(DebugForegroundColor, DebugBackgroundColor, string.Format("DEBUG: {0}", message));
#endif
#endif
}
// called by Write-Error
public override void WriteErrorLine(string value)
{
#if !noError
#if noConsole
MessageBox.Show(value, rawUI.WindowTitle, MessageBoxButtons.OK, MessageBoxIcon.Error);
#else
if (Console.IsErrorRedirected)
Console.Error.WriteLine(string.Format("ERROR: {0}", value));
else
WriteLineInternal(ErrorForegroundColor, ErrorBackgroundColor, string.Format("ERROR: {0}", value));
#endif
#endif
}
public override void WriteLine()
{
#if !noOutput
#if noConsole
MessageBox.Show("", rawUI.WindowTitle);
#else
Console.WriteLine();
#endif
#endif
}
public override void WriteLine(ConsoleColor foregroundColor, ConsoleColor backgroundColor, string value)
{
#if !noOutput
#if noConsole
if ((!string.IsNullOrEmpty(value)) && (value != "\n"))
MessageBox.Show(value, rawUI.WindowTitle);
#else
ConsoleColor fgc = Console.ForegroundColor, bgc = Console.BackgroundColor;
Console.ForegroundColor = foregroundColor;
Console.BackgroundColor = backgroundColor;
Console.WriteLine(value);
Console.ForegroundColor = fgc;
Console.BackgroundColor = bgc;
#endif
#endif
}
#if !noError && !noConsole
private void WriteLineInternal(ConsoleColor foregroundColor, ConsoleColor backgroundColor, string value)
{
ConsoleColor fgc = Console.ForegroundColor, bgc = Console.BackgroundColor;
Console.ForegroundColor = foregroundColor;
Console.BackgroundColor = backgroundColor;
Console.WriteLine(value);
Console.ForegroundColor = fgc;
Console.BackgroundColor = bgc;
}
#endif
// called by Write-Output
public override void WriteLine(string value)
{
#if !noOutput
#if noConsole
if ((!string.IsNullOrEmpty(value)) && (value != "\n"))
MessageBox.Show(value, rawUI.WindowTitle);
#else
Console.WriteLine(value);
#endif
#endif
}
#if noConsole
public Progress_Form pf = null;
#endif
public override void WriteProgress(long sourceId, ProgressRecord record)
{
#if noConsole
if (pf == null)
{
if (record.RecordType == ProgressRecordType.Completed) return;
pf = new Progress_Form(rawUI.WindowTitle, ProgressForegroundColor);
pf.Show();
}
pf.Update(record);
if (record.RecordType == ProgressRecordType.Completed)
{
if (pf.GetCount() == 0) pf = null;
}
#endif
}
// called by Write-Verbose
public override void WriteVerboseLine(string message)
{
#if !noOutput
#if noConsole
MessageBox.Show(message, rawUI.WindowTitle, MessageBoxButtons.OK, MessageBoxIcon.Information);
#else
WriteLine(VerboseForegroundColor, VerboseBackgroundColor, string.Format("VERBOSE: {0}", message));
#endif
#endif
}
// called by Write-Warning
public override void WriteWarningLine(string message)
{
#if !noError
#if noConsole
MessageBox.Show(message, rawUI.WindowTitle, MessageBoxButtons.OK, MessageBoxIcon.Warning);
#else
WriteLineInternal(WarningForegroundColor, WarningBackgroundColor, string.Format("WARNING: {0}", message));
#endif
#endif
}
}
internal class MainModule : PSHost
{
private MainAppInterface parent;
private MainModuleUI ui = null;
private CultureInfo originalCultureInfo = System.Threading.Thread.CurrentThread.CurrentCulture;
private CultureInfo originalUICultureInfo = System.Threading.Thread.CurrentThread.CurrentUICulture;
private Guid myId = Guid.NewGuid();
public MainModule(MainAppInterface app, MainModuleUI ui)
{
this.parent = app;
this.ui = ui;
}
public class ConsoleColorProxy
{
private MainModuleUI _ui;
public ConsoleColorProxy(MainModuleUI ui)
{
if (ui == null) throw new ArgumentNullException("ui");
_ui = ui;
}
public ConsoleColor ErrorForegroundColor
{
get { return _ui.ErrorForegroundColor; }
set { _ui.ErrorForegroundColor = value; }
}
public ConsoleColor ErrorBackgroundColor
{
get { return _ui.ErrorBackgroundColor; }
set { _ui.ErrorBackgroundColor = value; }
}
public ConsoleColor WarningForegroundColor
{
get { return _ui.WarningForegroundColor; }
set { _ui.WarningForegroundColor = value; }
}
public ConsoleColor WarningBackgroundColor
{
get { return _ui.WarningBackgroundColor; }
set { _ui.WarningBackgroundColor = value; }
}
public ConsoleColor DebugForegroundColor
{
get { return _ui.DebugForegroundColor; }
set { _ui.DebugForegroundColor = value; }
}
public ConsoleColor DebugBackgroundColor
{
get { return _ui.DebugBackgroundColor; }
set { _ui.DebugBackgroundColor = value; }
}
public ConsoleColor VerboseForegroundColor
{
get { return _ui.VerboseForegroundColor; }
set { _ui.VerboseForegroundColor = value; }
}
public ConsoleColor VerboseBackgroundColor
{
get { return _ui.VerboseBackgroundColor; }
set { _ui.VerboseBackgroundColor = value; }
}
public ConsoleColor ProgressForegroundColor
{
get { return _ui.ProgressForegroundColor; }
set { _ui.ProgressForegroundColor = value; }
}
public ConsoleColor ProgressBackgroundColor
{
get { return _ui.ProgressBackgroundColor; }
set { _ui.ProgressBackgroundColor = value; }
}
}
public override PSObject PrivateData
{
get
{
if (ui == null) return null;
return _consoleColorProxy ?? (_consoleColorProxy = PSObject.AsPSObject(new ConsoleColorProxy(ui)));
}
}
private PSObject _consoleColorProxy;
public override System.Globalization.CultureInfo CurrentCulture
{
get { return this.originalCultureInfo; }
}
public override System.Globalization.CultureInfo CurrentUICulture
{
get { return this.originalUICultureInfo; }
}
public override Guid InstanceId
{
get { return this.myId; }
}
public override string Name
{
get { return "PSRunspace-Host"; }
}
public override PSHostUserInterface UI
{
get { return ui; }
}
public override Version Version
{
get { return new Version(0, 5, 0, 33); }
}
public override void EnterNestedPrompt()
{ }
public override void ExitNestedPrompt()
{ }
public override void NotifyBeginApplication()
{
return;
}
public override void NotifyEndApplication()
{
return;
}
public override void SetShouldExit(int exitCode)
{
this.parent.ShouldExit = true;
this.parent.ExitCode = exitCode;
}
}
internal interface MainAppInterface
{
bool ShouldExit { get; set; }
int ExitCode { get; set; }
}
internal class MainApp : MainAppInterface
{
private bool shouldExit;
private int exitCode;
public bool ShouldExit
{
get { return this.shouldExit; }
set { this.shouldExit = value; }
}
public int ExitCode
{
get { return this.exitCode; }
set { this.exitCode = value; }
}
#if conHost
[DllImport("kernel32.dll", SetLastError = true)][return: MarshalAs(UnmanagedType.Bool)]
private static extern bool AllocConsole();
#endif
#if STA
[STAThread]
#else
[MTAThread]
#endif
private static int Main(string[] args)
{
#if conHost
// before this command no console should be attached or allocation fails
if (!AllocConsole()) { Console.Error.WriteLine("Creation of console failed!"); }
// connect STDIN
Console.SetIn(new System.IO.StreamReader(Console.OpenStandardInput()));
// connect STDOUT
System.IO.StreamWriter streamWriter = new System.IO.StreamWriter(Console.OpenStandardOutput());
streamWriter.AutoFlush = true;
Console.SetOut(streamWriter);
// connect STDERR
System.IO.StreamWriter errorWriter = new System.IO.StreamWriter(Console.OpenStandardOutput());
errorWriter.AutoFlush = true;
Console.SetError(errorWriter);
#endif
#if !noConsole && unicode
Console.OutputEncoding = new System.Text.UnicodeEncoding();
#endif
$culture
#if !noVisualStyles && noConsole
Application.EnableVisualStyles();
#endif
MainApp me = new MainApp();
bool paramWait = false;
string extractFN = string.Empty;
MainModuleUI ui = new MainModuleUI();
MainModule host = new MainModule(me, ui);
System.Threading.ManualResetEvent mre = new System.Threading.ManualResetEvent(false);
AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);
try
{
using (Runspace myRunSpace = RunspaceFactory.CreateRunspace(host))
{
#if STA
myRunSpace.ApartmentState = System.Threading.ApartmentState.STA;
#else
myRunSpace.ApartmentState = System.Threading.ApartmentState.MTA;
#endif
myRunSpace.Open();
// automatic variables
myRunSpace.SessionStateProxy.SetVariable("ScriptRoot", AppContext.BaseDirectory);
myRunSpace.SessionStateProxy.SetVariable("ScriptName", System.Reflection.Assembly.GetExecutingAssembly().Location);
myRunSpace.SessionStateProxy.SetVariable("rtmode", "exe");
using (PowerShell posh = PowerShell.Create())
{
#if !noConsole
Console.CancelKeyPress += new ConsoleCancelEventHandler(delegate(object sender, ConsoleCancelEventArgs e)
{
try
{
posh.BeginStop(new AsyncCallback(delegate(IAsyncResult r)
{
mre.Set();
e.Cancel = true;
}), null);
}
catch { };
});
#endif
posh.Runspace = myRunSpace;
posh.Streams.Error.DataAdded += new EventHandler<DataAddedEventArgs>(delegate(object sender, DataAddedEventArgs e)
{
ui.WriteErrorLine(((PSDataCollection<ErrorRecord>)sender)[e.Index].Exception.Message);
});
PSDataCollection<string> colInput = new PSDataCollection<string>();
if (Console.IsInputRedirected)
{ // read standard input
string sItem = "";
while ((sItem = Console.ReadLine()) != null)
{ // add to powershell pipeline
colInput.Add(sItem);
}
}
colInput.Complete();
PSDataCollection<PSObject> colOutput = new PSDataCollection<PSObject>();
colOutput.DataAdded += new EventHandler<DataAddedEventArgs>(delegate(object sender, DataAddedEventArgs e)
{
ui.WriteLine(((PSDataCollection<PSObject>)sender)[e.Index].ToString());
});
int separator = 0;
int idx = 0;
bool bHelp = false;
string sHelp = "";
foreach (string s in args)
{
if (string.Compare(s, "-wait", true) == 0) paramWait = true;
else if (s.StartsWith("-extract", StringComparison.InvariantCultureIgnoreCase))
{
string[] s1 = s.Split(new string[] { ":" }, 2, StringSplitOptions.RemoveEmptyEntries);
if (s1.Length != 2)
{
#if noConsole
MessageBox.Show("If you specify the -extract option you need to add a file for extraction in this way\r\n -extract:\"<filename>\"", System.AppDomain.CurrentDomain.FriendlyName, MessageBoxButtons.OK, MessageBoxIcon.Error);
#else
Console.WriteLine("If you specify the -extract option you need to add a file for extraction in this way\r\n -extract:\"<filename>\"");
#endif
return 1;
}
extractFN = s1[1].Trim(new char[] { '\"','\'' });
}
else if (string.Compare(s, "-end", true) == 0)
{
separator = idx + 1;
break;
}
else if (string.Compare(s, "-?", true) == 0) bHelp = true;
else if (bHelp)
{
if ((string.Compare(s, "-detailed", true) == 0) || (string.Compare(s, "-examples", true) == 0) || (string.Compare(s, "-full", true) == 0)) sHelp = s;
}
else if (string.Compare(s, "-debug", true) == 0)
{
System.Diagnostics.Debugger.Launch();
break;
}
idx++;
}
Assembly executingAssembly = Assembly.GetExecutingAssembly();
$embedFileSection
string script = Expand_text(@"$sourceScript");
if (!string.IsNullOrEmpty(extractFN))
{
System.IO.File.WriteAllText(extractFN, script);
return 0;
}
#if psCore
string modulePath = "`$env:PSModulePath = \"" + GetPowershellModulePath() + "\"";
posh.AddScript(modulePath);
#endif
if (bHelp)
{ // help selected
posh.AddScript("function " + System.AppDomain.CurrentDomain.FriendlyName + "{" + script + "}; Get-Help " + System.AppDomain.CurrentDomain.FriendlyName + " " + sHelp + " | Out-String");
} else { // execution selected
posh.AddScript(script);
}
if (!bHelp)
{ // only if no help selected
// parse parameters
string argbuffer = null;
// regex for named parameters
System.Text.RegularExpressions.Regex regex = new System.Text.RegularExpressions.Regex(@"^-([^: ]+)[ :]?([^:]*)$");
for (int i = separator; i < args.Length; i++)
{
System.Text.RegularExpressions.Match match = regex.Match(args[i]);
double dummy;
if ((match.Success && match.Groups.Count == 3) && (!Double.TryParse(args[i], out dummy)))
{ // parameter in powershell style, means named parameter found
if (argbuffer != null) // already a named parameter in buffer, then flush it
posh.AddParameter(argbuffer);
if (match.Groups[2].Value.Trim() == "")
{ // store named parameter in buffer
argbuffer = match.Groups[1].Value;
}
else
// caution: when called in powershell $TRUE gets converted, when called in cmd.exe not
if ((match.Groups[2].Value == "$TRUE") || (match.Groups[2].Value.ToUpper() == "\x24TRUE"))
{ // switch found
posh.AddParameter(match.Groups[1].Value, true);
argbuffer = null;
}
else
// caution: when called in powershell $FALSE gets converted, when called in cmd.exe not
if ((match.Groups[2].Value == "$FALSE") || (match.Groups[2].Value.ToUpper() == "\x24"+"FALSE"))
{ // switch found
posh.AddParameter(match.Groups[1].Value, false);
argbuffer = null;
}
else
{ // named parameter with value found
posh.AddParameter(match.Groups[1].Value, match.Groups[2].Value);
argbuffer = null;
}
}
else
{ // unnamed parameter found
if (argbuffer != null)
{ // already a named parameter in buffer, so this is the value
posh.AddParameter(argbuffer, args[i]);
argbuffer = null;
}
else
{ // position parameter found
posh.AddArgument(args[i]);
}
}
}
if (argbuffer != null) posh.AddParameter(argbuffer); // flush parameter buffer...
// convert output to strings
posh.AddCommand("Out-String");
// with a single string per line
posh.AddParameter("Stream");
}
posh.BeginInvoke<string, PSObject>(colInput, colOutput, null, new AsyncCallback(delegate(IAsyncResult ar)
{
if (ar.IsCompleted) mre.Set();
}), null);
while (!me.ShouldExit && !mre.WaitOne(100)) { };
posh.Stop();
if (posh.InvocationStateInfo.State == PSInvocationState.Failed)
ui.WriteErrorLine(posh.InvocationStateInfo.Reason.Message);
}
myRunSpace.Close();
}
}
catch (Exception ex)
{
#if !noError
#if noConsole
MessageBox.Show("An exception occured: " + ex.Message, System.AppDomain.CurrentDomain.FriendlyName, MessageBoxButtons.OK, MessageBoxIcon.Error);
#else
Console.Write("An exception occured: ");
Console.WriteLine(ex.Message);
#endif
#endif
}
if (paramWait)
{
#if !noError
#if noConsole
MessageBox.Show("Click OK to exit...", System.AppDomain.CurrentDomain.FriendlyName);
#else
Console.WriteLine("Hit any key to exit...");
Console.ReadKey();
#endif
#endif
}
return me.ExitCode;
} // Main
#if psCore
// two functions below taken from https://github.com/FabienTschanz/PS2EXE.Core
private static string GetPowershellModulePath()
{
var environment = new StringBuilder();
// Get the default windows modules path
var winModules = Path.Combine(Environment.ExpandEnvironmentVariables("%WINDIR%"),
"system32", "WindowsPowerShell", "v1.0", "Modules");
// Add default Windows modules
var env = Environment.GetEnvironmentVariable("PSModulePath");
if (!string.IsNullOrEmpty(env)) environment.Append(env);
if (!environment.ToString().Contains(winModules, StringComparison.OrdinalIgnoreCase))
environment.Append(';').Append(winModules);
// Get PowerShell SDK modules path
var bundlePath = GetBundleExtractLocation();
var extractLocation = Path.Combine(bundlePath, "runtimes", "win", "lib", "net$dotnetversion", "Modules");
if (System.IO.Directory.Exists(extractLocation))
environment.Append(';').Append(extractLocation);
return environment.ToString();
}
private static string? GetBundleExtractLocation()
{
// Get extract bundle extract location
// Docs: https://learn.microsoft.com/en-us/dotnet/core/deploying/single-file/overview?tabs=cli#native-libraries
string? assemblyLocation = null;
if (string.IsNullOrEmpty(assemblyLocation))
assemblyLocation = Assembly.GetEntryAssembly()?.Location;
if (string.IsNullOrEmpty(assemblyLocation))
assemblyLocation = Assembly.GetExecutingAssembly().Location;
if (string.IsNullOrEmpty(assemblyLocation))
assemblyLocation = AppContext.BaseDirectory;
// Get the base folder of the assembly location
var baseFolder = Path.GetDirectoryName(assemblyLocation);
if (!string.IsNullOrEmpty(baseFolder) && !Directory.Exists(baseFolder)) return null;
return baseFolder;
}
#endif
static string Expand_text(string InputString)
{
if (string.IsNullOrEmpty(InputString)) return null;
string result;
var compressedStream = new MemoryStream(Convert.FromBase64String(InputString));
using (var decompressorStream = new DeflateStream(compressedStream, CompressionMode.Decompress))
{
using (var decompressedStream = new MemoryStream())
{
decompressorStream.CopyTo(decompressedStream);
result = Encoding.UTF8.GetString(decompressedStream.ToArray());
}
}
return result;
} // Expand_text
static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
throw new Exception("Unhandled exception in " + System.AppDomain.CurrentDomain.FriendlyName);
}
}
}
"@.split("`n").where({$_}).foreach({
$l = $_.trim()
if ($l -notmatch '^$|^(//|#reg|#endreg)') {$l}
<#if ($l) { TEST!!!
if ([regex]::IsMatch($l,'//')) { #, [System.Text.RegularExpressions.RegexOptions]::Compiled)
$l = [regex]::replace($l,'//.*$','').TrimStart()
if ($l) {$l}
} else {$l}
}#>
}) -join "`n"
} # END Get-MainProgramSource
function Show-ModuleHelp {
$colw = 18
$col1 = [string]::format("`$1`n{0}", ' '*($colw + 3))
##$rx1 = [regex]::new("(^.{1,$([console]::WindowWidth)} (?!$))", [System.Text.RegularExpressions.RegexOptions]::Compiled)
$rx1 = [regex]::new("(^.{$([console]::WindowWidth)}(?!$))", [System.Text.RegularExpressions.RegexOptions]::Compiled)
$fmt1 = "{0,-$colw} : {1}"
$fmt11 = "{0,-$colw} {1}"
$fmtpad2 = '{0}'
$fmtpad6 = ' {0}'
$util = 'invoke-ps2exe'
$titleExm = 'DarkGray'
function write-format {
Write-Host $rx1.Replace(($fmt1 -f $args),$col1)
} # end
function write-format1 {
Write-Host $rx1.Replace(($fmt11 -f $args),$col1)
} # end
Write-Host "USAGE:" -ForegroundColor DarkYellow
Write-Host "Invoke-ps2exe [-InputFile] <fullname> [-OutputFile <filepath>]"
Write-Host " [-ScriptBlock] <script> [-OutputFile <filepath>]"
Write-Host " [-EmbedFiles <hashtable>] [-IconFile {<fullname> | system}]"
Write-Host " [-Title <string>] [-Description <string>] [-Version <string>]"
Write-Host " [-Company <string>] [-Product <string>] [-Copyright <string>] [-Trademark <string>]"
Write-Host " [-Core] [-TargetOS {w[indows] | l[inux] | m[acos]}] [-Arm] [-DotnetVersion <string>] [-Solo] [-NoCache]"
Write-Host " [-Platform {x64|x86|any}] [-MTA] [-Lcid <lang_id>] [-LongPaths]"
Write-Host " [-NoOutput] [-NoError] [-NoConsole] [-ConHost] [-Unicode]"
Write-Host " [-DPIAware] [-WinFormsDPIAware] [-NoVisualStyles] [-ExitOnCancel]"
Write-Host " [-CredentialGUI] [-RequireAdmin] [-SupportOS] [-Virtualize]"
Write-Host " [-PrepareDebug] [-ConfigFile] [-NoCompress] [-Basic] [-Run]"
#Write-Host " [-Profile {gui | console | winforms}]"
Write-Host
Write-Host 'KEY FEATURES:' -ForegroundColor DarkYellow
Write-Host '- No external dependencies'
Write-Host '- Deployment strategy for output file: single executable'
Write-Host '- Source compressor & encoder'
Write-Host '- Automatic variables in user PS script: $ScriptRoot, $ScriptName, $RtMode'
Write-Host '- Experimental support for PowerShell Core (see Notes)'
Write-Host '- Full backward compatibility with original PS2EXE PS script'
Write-Host '- There is a GUI counterpart of the ps2exec — Ps2exe.NET'
Write-Host
Write-Host 'PARAMETERS:' -ForegroundColor DarkYellow
write-format "-InputFile" "PowerShell script filename that you want to convert to executable (file has to be UTF8 or UTF16 encoded)"
write-format "-ScriptBlock" "PowerShell script that you want to convert to executable"
write-format "-OutputFile" "Destination executable file name or folder, defaults to inputFile with extension '.exe'"
write-format "-IconFile" "Icon file name for the compiled executable or 'system' to set default, otherwise the icon from powershell.exe will be used"
write-format "-EmbedFiles" "Files to embed given as a hashtable, will be extracted to the key of the hashtable, source file names must be unique"
write-format1 "" "(e.g. -embedFiles @{'Targetfilepath'='Sourcefilepath'})"
write-format "-Title" "Title information (displayed in details tab of Windows Explorer's properties dialog)"
write-format "-Description" "Description information (not displayed, but embedded in executable)"
write-format "-Company" "Company information (not displayed, but embedded in executable)"
write-format "-Product" "Product information (displayed in details tab of Windows Explorer's properties dialog)"
write-format "-Copyright" "Copyright information (displayed in details tab of Windows Explorer's properties dialog)"
write-format "-Trademark" "Trademark information (displayed in details tab of Windows Explorer's properties dialog)"
write-format "-Version" "Version information (displayed in details tab of Windows Explorer's properties dialog)"
write-format "-Lcid" "Locale ID decimal notation for the compiled executable. Current user culture applies if not specified"
write-format "-Core" "EXPERIMENTAL support for PowerShell Core scripts"
write-format "-TargetOS" "Option for -Core. Valid values are Windows, Linux, Macos. Default is Windows"
write-format "-Arm" "Option for -Core. Enable compiler to create executable for ARM platforms"
write-format "-DotnetVersion" "Option for -Core. Target .NET version"
write-format "-Solo" "Option for -Core. Enable 'SelfContained' mode"
write-format "-NoCache" "Option for -Core. Omit checking for changes in compiler parameters"
write-format "-MTA" "Enable Multi Thread Apartment mode. Default is Single Thread Apartment (STA). This parameter replaces separate 'STA', but that can be used for backward compatibility"
write-format "-Platform" "Compile for 32-bit or 64-bit runtime, or AnyCPU. This parameter replaces 'x86' and 'x64' separate ones, but those can be used for backward compatibility"
write-format "-NoConsole" "The resulting executable will be a GUI app without a console window"
write-format "-ConHost" "Force start with conhost as console instead of Windows Terminal (disables redirections)"
write-format "-NoOutput" "The resulting executable will generate no standard output (includes verbose and information channel)"
write-format "-NoError" "The resulting executable will generate no error output (includes warning and debug channel)"
write-format "-Unicode" "Encode output as Unicode in console mode"
write-format "-CredentialGUI" "Use GUI for prompting credentials in console mode"
write-format "-PrepareDebug" "Create helpful information for debugging"
write-format "-ConfigFile" "Write a config file (<outputfile>.exe.config)"
write-format "-NoVisualStyles" "Disable visual styles for a generated windows GUI application (only with -noConsole)"
write-format "-ExitOnCancel" 'Exit program when Cancel or "✖" is selected in a Read-Host input box (only with -NoConsole)'
write-format "-DPIAware" "If display scaling is enabled, GUI controls will be scaled if possible"
write-format "-WinFormsDPIAware" "If display scaling is enabled, WinForms use DPI scaling (requires Windows 10 and .Net 4.7 or up)"
write-format "-RequireAdmin" "If UAC is enabled, compiled executable run only in elevated context (UAC dialog appears if required)"
write-format "-SupportOS" "Use features of the latest Windows versions (run [Environment]::OSVersion to see the difference)"
write-format "-LongPaths" "Enable long paths (beyond 260 characters) if enabled on OS (works only with Windows 10 and up)"
write-format "-Virtualize" "Application virtualization is activated (forcing x86 runtime)"
write-format "-NoCompress" "Disable a PS compression of the source. Text compression is always on"
write-format "-Basic" "Use a light compression mode. Omits comments and indented formatting in the output string. Ignored if -NoCompress specified"
write-format "-Run" "Invoke output executable immediately after creation"
#write-format "-Profile" "Selection shortcut. Group selection of parameters. Any other parameter takes precedence over this parameter"
Write-Host
Write-Host 'NOTES:' -ForegroundColor DarkYellow
Write-Host 'Executable uses Windows PowerShell host to run the embeded script'
Write-Host 'Executable takes 2-4 sec to create Windows PowerShell host before starting the embeded script'
Write-Host 'In compiled executable some of the PowerShell related automatic variables are no longer available. Especially the $PSScriptRoot variable is empty. It replaced with $ScriptRoot'
Write-Host 'PowerShell Core profile: PublishSingleFile, net8+, x64'
Write-Host 'PowerShell Core support requires the public NuGet repository available at compiling runtime'
Write-Host
Write-Host 'EXAMPLES:' -ForegroundColor DarkYellow
Write-Host ($fmtpad2 -f "- Compile GUI application") -ForegroundColor $titleExm
Write-Host ($fmtpad6 -f "$util -input C:\myscripts\myguiapp.ps1 -icon C:\myscripts\appicon.ico -noconsole -noerror -nooutput")
Write-Host
$defparams = $script:MyInvocation.MyCommand.ScriptBlock.Ast.FindAll({
$args[0] -is [System.Management.Automation.Language.ParameterAst]
}, $false).where{$_.DefaultValue}
if ($defparams) {
Write-Host 'DEFAULT VALUES:' -ForegroundColor DarkYellow
$defparams.DefaultValue | ForEach-Object {
write-format ('-{0}' -f $_.Parent.Name.VariablePath.UserPath) $_.Value
}
Write-Host
}
Write-Warning "Input file or scriptblock not specified."
} # END Show-ModuleHelp
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment