From 7c1d0b17773dc9478365bc18566bdbda1b816013 Mon Sep 17 00:00:00 2001 From: "tyler.montney" Date: Sun, 24 Dec 2023 21:57:25 -0600 Subject: [PATCH] Simplified process by passing script as encoded string, including arguments --- PS in Batch.ps1 | 117 +++++++++++++------------------------------ Sample PS Script.ps1 | 19 +++---- 2 files changed, 43 insertions(+), 93 deletions(-) diff --git a/PS in Batch.ps1 b/PS in Batch.ps1 index 13392db..fc31a88 100644 --- a/PS in Batch.ps1 +++ b/PS in Batch.ps1 @@ -1,92 +1,44 @@ -function ConvertTo-BatchWrapped([String]$PSScriptPath, [Hashtable]$Arguments) { - $PSScriptContents = Get-Content -Path $PSScriptPath -Raw -ErrorAction Stop - $BatchBase64 = ConvertTo-MultilineBatchBase64 -VarName "TargetScript" -VarText $PSScriptContents - - $TempBase64OutputLine = @() - for ($i = 1; $i -le $BatchBase64.VarCount; $i++) { - $TempBase64OutputLine += "echo !TargetScript$i! > %TempBase64Output%" +function ConvertTo-PSBatchScript { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $true)] + [String] + $PSScriptContents, + [Parameter(Mandatory = $false)] + [String[]] + $Arguments = @(), + # Consider using delayed expansion on variables + # If you explicitly specify $env:TEMP and run this as another user, it will map to the wrong user's temp folder + [Parameter(Mandatory = $false)] + [String] + $CurrentWorkingDirectory = "%TEMP%" + ) + + try + { [void]([ScriptBlock]::Create($PSScriptContents)) } + catch + { Write-Warning -Message "There was a problem parsing 'PSScriptContents' as a PowerShell script."; Write-Error $_; return } + + if ($Arguments.Count -gt 0) { + $ArgumentsAL = [System.Collections.ArrayList]::new() + $ArgumentsAL.AddRange($Arguments) + + $cliXml = [System.Management.Automation.PSSerializer]::Serialize($ArgumentsAL) + $ArgsBase64 = "-EncodedArguments " + ([Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes($cliXml))) } - $InvokePSLine = "%WINDIR%\System32\WindowsPowerShell\v1.0\powershell.exe -ExecutionPolicy Bypass -File ""%TempTargetScript%""" - if ($Arguments) { - $Arguments.Keys | ForEach-Object { - if ($Arguments[$_] -is [String]) { - $InvokePSLine += " -$_ ""$($Arguments[$_])""" - } - else { - $InvokePSLine += " -$_ $($Arguments[$_])" - } - } - } + $BatchBase64 = [Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes($PSScriptContents)) + $InvokePSLine = "%WINDIR%\System32\WindowsPowerShell\v1.0\powershell.exe -ExecutionPolicy Bypass -EncodedCommand $BatchBase64 $ArgsBase64" - $BatchContents = @" + return @" @echo off -setlocal EnableDelayedExpansion -set LF=^ - - -REM !!! Do not remove the two lines above this; required to make a newline variable !!! - -REM To generate the following, use certutil -decode or ConvertTo-MultilineBatchBase64 in PowerShell.Common.psm1 -REM At this time, it isn't possible to decode with certutil using Base64 encoded by [System.Convert]::ToBase64String -REM certutil appears to keep lines to ~64 characters; recommended maximium line length is 127 -REM Set the Base64 of the target script -$($BatchBase64.Var) +REM Set current working directory to a temporary folder +cd $CurrentWorkingDirectory -REM Set the temporary file paths -set "TempBase64Output=%temp%\TargetScript.%random%.txt" -set "TempTargetScript=%temp%\TargetScript.%random%.ps1" - -REM Decode the TargetScript variable -$($TempBase64OutputLine -join "`n") -certutil -decode %TempBase64Output% %TempTargetScript% > NUL - -REM Execute TargetScript +REM Execute PowerShell script $InvokePSLine - -REM Clean up temporary files -del %TempBase64Output% -del %TempTargetScript% "@ - - Set-Content -Path "$Script:ScriptCWD\$(([System.IO.Path]::GetFileNameWithoutExtension($PSScriptPath))).bat" -Value $BatchContents -Force -} - -function ConvertTo-MultilineBatchBase64([String]$VarName, [String]$VarText) { - #$VarBase64 = [System.Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes($Text)) - - $VarTextTempFile = New-TemporaryFile - $VarBase64TempFile = New-TemporaryFile - Set-Content -Path $VarTextTempFile -Value $VarText - [void](certutil.exe -f -encode $VarTextTempFile $VarBase64TempFile) - - $Var = @() - $VarCount = 1 - $VarBase64 = Get-Content -Path $VarBase64TempFile | Select-Object -Skip 1 | Select-Object -SkipLast 1 - - Remove-Item -Path $VarTextTempFile -Force -ErrorAction SilentlyContinue - Remove-Item -Path $VarBase64TempFile -Force -ErrorAction SilentlyContinue - - if ($VarBase64) { - $Var = @("set $VarName$VarCount=$($VarBase64[0])!LF!") - if ($VarBase64.Length -gt 1) { - for ($i = 1; $i -lt $VarBase64.Length; $i++) { - if ($i % 100 -eq 0) { - $VarCount += 1 - $Var += "set $VarName$VarCount=$($VarBase64[$i])!LF!" - } - else { - $Var += "set $VarName$VarCount=!$VarName$VarCount!$($VarBase64[$i])!LF!" - } - } - } - } - - return [PSCustomObject]@{ - VarCount = $VarCount - Var = ($Var -join "`n") - } } #################### @@ -101,4 +53,5 @@ else { throw "Cannot determine script's working directory" } -ConvertTo-BatchWrapped -PSScriptPath "$Script:ScriptCWD\Sample PS Script.ps1" -Arguments @{"Message" = "Hello world!" } \ No newline at end of file +$PSScriptContents = Get-Content -Path "$Script:ScriptCWD\Sample PS Script.ps1" -Raw +ConvertTo-PSBatchScript -PSScriptContents $PSScriptContents -Arguments @("Hello world!") | Set-Content -LiteralPath "$Script:ScriptCWD\Sample PS Script.bat" -Force \ No newline at end of file diff --git a/Sample PS Script.ps1 b/Sample PS Script.ps1 index d0107cf..262108d 100644 --- a/Sample PS Script.ps1 +++ b/Sample PS Script.ps1 @@ -1,19 +1,16 @@ [CmdletBinding()] param ( - [Parameter(Mandatory = $true)] + [Parameter(Mandatory = $false)] [String] - $Message + $Message = "Hello" ) -if ($MyInvocation.MyCommand.Path) { - $ScriptCWD = (Get-Item -Path $MyInvocation.MyCommand.Path).Directory.FullName -} -elseif ($PSScriptRoot) { - $ScriptCWD = $PSScriptRoot -} -else { - throw "Cannot determine script's working directory" -} +# For whatever reason it wasn't changed in the batch script +# We don't want to dirty up System32 +if ((Get-Location).Path -eq "$env:WINDIR\System32") +{ Set-Location -Path $env:TEMP } + +$ScriptCWD = (Get-Location).Path Write-Output -InputObject "Script current working directory: $ScriptCWD"