exe21sp.ps1
|
#Requires -Version 5.1 <# .SYNOPSIS Restore equivalent ps1 from exe built by ps12exe (exe -> ps1). .DESCRIPTION Uses AsmResolver to read script payload from exe: - For exe built with standard program frame (CodeDom/CodeAnalysis): reads embedded .NET manifest resource "main.par", GZip-decompresses and decodes as UTF-8 to get the original script. - For minimal exe compiled with TinySharp: parses its CIL and PE image, restores the output string and exit code captured by TinySharp, and generates a minimal ps1 containing only that string (and optional exit statement) to equivalently reproduce the behavior. .PARAMETER inputFile Path or URL to the .exe file to decompile. .PARAMETER outputFile Optional path for the output ps1 file. If not specified, writes to stdout when redirected, otherwise writes to <exe>.ps1 in the same directory as the input. .EXAMPLE exe21sp -inputFile .\myapp.exe exe21sp -inputFile .\myapp.exe -outputFile .\myapp.ps1 .EXAMPLE Get-ChildItem *.exe | exe21sp ".\app.exe" | exe21sp #> [CmdletBinding()] param( [Parameter(ValueFromPipeline=$true)] [string[]]$inputFile, [string]$outputFile, #_if PSScript [ArgumentCompleter({ Param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters) . "$PSScriptRoot\src\LocaleArgCompleter.ps1" @PSBoundParameters })] #_endif [string]$Localize, [switch]$help ) #_if PSScript $global:LastExitCode = 0 $LocalizeData = . $PSScriptRoot\src\LocaleLoader.ps1 -Localize $Localize . $PSScriptRoot\src\WriteI18n.ps1 Set-I18nData -I18nData $LocalizeData.exe21spI18nData function Show-exe21spHelp { . $PSScriptRoot\src\HelpShower.ps1 -HelpData $LocalizeData.exe21spHelpData | Write-Host } if ($help) { Show-exe21spHelp return } $inputItemsToProcess = @($inputFile) + @($input) | Where-Object { $_ } if ($outputFile -and ($outputFile = $outputFile.Trim()) -and $outputFile -notmatch '\.ps1$') { $outputFile = $outputFile + '.ps1' } if ($inputItemsToProcess.Count -eq 0) { Show-exe21spHelp Write-Host Write-I18n Error NoneInput -Category InvalidArgument if ([System.Console]::IsOutputRedirected -or [System.Console]::IsInputRedirected -or [System.Console]::IsErrorRedirected) { $global:LastExitCode = 2 return } & $PSScriptRoot\src\Interact\exe21sp.ps1 -Localize $Localize return } function Resolve-ExeInputPath { param([string]$PathOrUrl) if ($PathOrUrl -match "^(https?|ftp)://") { $tempFile = [System.IO.Path]::GetTempFileName() try { Invoke-WebRequest -Uri $PathOrUrl -OutFile $tempFile -ErrorAction Stop return [PSCustomObject]@{ Path = $tempFile; IsTemp = $true } } catch { Remove-Item -LiteralPath $tempFile -Force -ErrorAction SilentlyContinue throw } } $resolved = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($PathOrUrl) return [PSCustomObject]@{ Path = $resolved; IsTemp = $false } } $Refs = @( 'System', 'System.Core', 'System.IO.Compression', 'netstandard, Version=2.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51' ) Get-ChildItem -LiteralPath $PSScriptRoot\src\bin\AsmResolver -Recurse -Filter *.dll | ForEach-Object { $Refs += $_.FullName try { Add-Type -LiteralPath $_.FullName -ErrorVariable $null } catch { $_.Exception.LoaderExceptions | Out-String | Write-Verbose $Error.Remove($_) } } $ExtractorCode = Get-Content -LiteralPath $PSScriptRoot\src\programFrames\exe21sp.cs -Raw -Encoding UTF8 Add-Type -TypeDefinition $ExtractorCode -ReferencedAssemblies $Refs -IgnoreWarnings . $PSScriptRoot\src\TaskbarProgress.ps1 $total = $inputItemsToProcess.Count $currentIndex = 0 foreach ($currentInput in $inputItemsToProcess) { Write-TaskbarProgress -Percent ([Math]::Min(100, [int](($currentIndex / $total) * 100))) $resolved = $null try { $resolved = Resolve-ExeInputPath -PathOrUrl $currentInput } catch { if ($currentInput -match "^(https?|ftp)://") { Write-I18n Error InputUrlFailed $currentInput } else { Write-I18n Error FileNotFound $currentInput } $global:LastExitCode = 3 Write-TaskbarProgressError $currentIndex++ continue } $currentExe = $resolved.Path if (-not (Test-Path -LiteralPath $currentExe -PathType Leaf)) { Write-I18n Error FileNotFound $currentInput $global:LastExitCode = 3 Write-TaskbarProgressError if ($resolved.IsTemp) { Remove-Item -LiteralPath $currentExe -Force -ErrorAction SilentlyContinue } $currentIndex++ continue } try { $script = [exe21sp.Extractor]::ExtractScriptFromExe($currentExe) } catch { $msg = $_.Exception.Message if ($LocalizeData.exe21spI18nData.ContainsKey($msg)) { Write-I18n Error $msg } else { Write-Error $msg } $global:LastExitCode = 1 Write-TaskbarProgressError if ($resolved.IsTemp) { Remove-Item -LiteralPath $currentExe -Force -ErrorAction SilentlyContinue } $currentIndex++ continue } if ($resolved.IsTemp) { Remove-Item -LiteralPath $currentExe -Force -ErrorAction SilentlyContinue } if ($null -eq $script) { Write-I18n Error NoEmbeddedScript $currentInput $global:LastExitCode = 1 Write-TaskbarProgressError $currentIndex++ continue } $currentOutFile = $outputFile if (-not $currentOutFile -and -not ([System.Console]::IsOutputRedirected -or [System.Console]::IsInputRedirected -or [System.Console]::IsErrorRedirected)) { $baseName = if ($currentInput -match "^(https?|ftp)://") { [System.IO.Path]::GetFileNameWithoutExtension([System.Uri]::new($currentInput).Segments[-1]) } else { [System.IO.Path]::GetFileNameWithoutExtension($ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($currentInput)) } $dir = if ($currentInput -match "^(https?|ftp)://") { $PWD.Path } else { [System.IO.Path]::GetDirectoryName($ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($currentInput)) } $currentOutFile = [System.IO.Path]::Combine($dir, "$baseName.ps1") } if ($currentOutFile) { $currentOutFile = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($currentOutFile) [System.IO.File]::WriteAllText($currentOutFile, $script, [System.Text.UTF8Encoding]::new($false)) Write-Verbose "Written to $currentOutFile" if ([System.Console]::IsOutputRedirected -or [System.Console]::IsInputRedirected -or [System.Console]::IsErrorRedirected) { Write-Output $currentOutFile } } else { Write-Output $script } $currentIndex++ } Write-TaskbarProgress -Percent 100 Write-TaskbarProgressClear #_else #_require ps12exe #_!!exe21sp @PSBoundParameters #_endif |