psbundle.psm1
|
$psBundleLoader = @'
# psbundle loader Set-ExecutionPolicy Bypass -Force -Scope Process if($null -eq $env:PSBundlePath){ $env:PSModulePath = (Resolve-Path 'psbundle_modules').Path + ';' + $env:PSModulePath }else{ $env:PSModulePath = (Resolve-Path (Join-Path $env:PSBundlePath 'psbundle_modules')).Path + ';' + $env:PSModulePath } '@ $psBundleExeLdr = @' using System.IO; using System.Reflection; using System.Diagnostics; class _PSBundleAutoGeneratedExeLdr {{ static readonly string tempDir = Path.Combine(Path.GetTempPath(), "psb{0}"); static readonly Assembly executingAssembly = Assembly.GetExecutingAssembly(); static void ExtractFile(string fileName){{ Directory.CreateDirectory(Path.Combine(tempDir, Path.GetDirectoryName(fileName))); Stream stream = executingAssembly.GetManifestResourceStream(fileName); byte[] data = new byte[stream.Length]; stream.Read(data, 0, (int)stream.Length); stream.Close(); File.WriteAllBytes(Path.Combine(tempDir, fileName), data); }} public static void Main(){{ if(!Directory.Exists(tempDir)){{ {1} }} ProcessStartInfo psi = new ProcessStartInfo(); psi.FileName = "cmd.exe"; psi.Arguments = "/c {2}"; psi.EnvironmentVariables.Add("PSBundlePath", tempDir); psi.UseShellExecute = false; psi.CreateNoWindow = {3}; Process.Start(psi).WaitForExit(); }} }} '@ $alphabet = @('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9') Function Get-RandomString { Param( [Parameter(Position=0)] [int] $Count = 8 ) return '_' + ((Get-Random -Count ($Count - 1) -InputObject $alphabet) -join '') } Function Invoke-Csc { Param( [Parameter(Mandatory, Position=0)] [string] $Arguments, [Parameter(Position=1)] [string] $WorkingDirectory = (Resolve-Path '.') ) $cscDirName = Get-ChildItem -Path "$Env:SYSTEMROOT\Microsoft.NET\Framework64\v*" -Name | Sort-Object { [version]$_.Substring(1) } -Descending | Select-Object -First 1 $cscExeName = Join-Path "$Env:SYSTEMROOT\Microsoft.NET\Framework64" $cscDirName 'csc.exe' $psi = [System.Diagnostics.ProcessStartInfo]::new() $psi.FileName = $cscExeName $psi.Arguments = $Arguments $psi.WorkingDirectory = $WorkingDirectory $process = [System.Diagnostics.Process]::Start($psi) $process.WaitForExit() } Function New-BundledExe { [CmdletBinding()] Param( [Parameter(Mandatory)] [string] $Command, [Parameter(Mandatory)] [string] $OutputPath, [hashtable] $IncludeFiles = @(), [switch] $HideConsole ) $codeTmpFile = (New-TemporaryFile).FullName $cmdTmpFile = (New-TemporaryFile).FullName $sbCode = [System.Text.StringBuilder]::new() $sbCmd = [System.Text.StringBuilder]::new() $null = $sbCmd.AppendLine('/nologo') $null = $sbCmd.AppendLine("`"/out:$OutputPath`"") if($HideConsole.IsPresent){ $null = $sbCmd.AppendLine("/target:winexe") } $IncludeFiles.Keys | ForEach-Object { $name = $_.Replace('\', '\\') $null = $sbCode.AppendLine("ExtractFile(`"$name`");") $null = $sbCmd.AppendLine("`"/resource:$($IncludeFiles[$_]),$_`"") } $null = $sbCmd.AppendLine("`"$codeTmpFile`"") $code = [string]::Format($psBundleExeLdr, (Get-RandomString -Count 10), $sbCode.ToString(), $Command, $HideConsole.IsPresent.ToString().ToLower()) $code | Out-File $codeTmpFile $sbCmd.ToString() | Out-File $cmdTmpFile Invoke-Csc -Arguments "`"@$cmdTmpFile`"" Remove-Item -Force $cmdTmpFile Remove-Item -Force $codeTmpFile } Function New-PSBundle { [CmdletBinding()] Param( [Parameter(Position=0)] [string] $Path = '.' ) $null = New-Item -ItemType Directory -Path (Join-Path $Path 'psbundle_modules') $null = New-Item -ItemType Directory -Path (Join-Path $Path 'dist') } Function Install-PSBundleModule { [CmdletBinding()] Param( [Parameter(Mandatory, Position=0)] [string[]] $ModuleName, [string] $Path = '.' ) Save-Module -Name $ModuleName -Path (Join-Path $Path 'psbundle_modules') -Force -AcceptLicense } Function Build-PSBundle { [CmdletBinding()] Param( [string] $Path = '.', [ValidateSet('ps1', 'bat', 'exe')] [string] $Target = 'ps1', [ValidateSet('Desktop', 'Core')] [string] $PSEdition = 'Core', [switch] $BundlePSCore, [switch] $HideConsole ) $sb = [System.Text.StringBuilder]::new() $null = $sb.AppendLine($psBundleLoader) Get-ChildItem -Path $Path -Include '*.ps1' -Recurse -Name | ` Where-Object { -not $_.StartsWith('psbundle_modules') -and -not $_.StartsWith('dist') } | ` ForEach-Object { $null = $sb.AppendLine((Get-Content -Raw -Path $_)) } Set-Content -Path (Join-Path $Path 'dist' 'bundle.ps1') -Value $sb.ToString() switch($Target){ 'exe' { Write-Verbose 'Selected target: EXE' $psFileName = 'powershell.exe' if($PSEdition -eq 'Core'){ if($BundlePSCore.IsPresent){ $psFileName = '..\\pwsh\\pwsh.exe' }else{ $psFileName = 'pwsh.exe' } } $fileList = @{} Get-ChildItem (Join-Path $Path 'dist') -Recurse -File -Name | ` ForEach-Object { $file = Join-Path 'dist' $_ $fileList.Add($file, $file) } Get-ChildItem (Join-Path $Path 'psbundle_modules') -Recurse -File -Name | ` ForEach-Object { $file = Join-Path 'psbundle_modules' $_ $fileList.Add($file, $file) } if($BundlePSCore.IsPresent){ Get-ChildItem $PSHome -Recurse -File -Name | ForEach-Object { $fileList.Add((Join-Path 'pwsh' $_), (Join-Path $PSHome $_)) } } $fileList | ForEach-Object { Write-Verbose "Adding file: $_" } New-BundledExe -OutputPath (Join-Path $Path 'dist' 'bundle.exe') -Command "cd /d \`"%PSBundlePath%\\dist\`" & $psFileName -ExecutionPolicy bypass -File bundle.ps1" -IncludeFiles $fileList -HideConsole:$HideConsole.IsPresent } } } Function Invoke-PSBundle { [CmdletBinding()] [Alias('psbundle')] Param( [Parameter(Mandatory, Position=0)] [string] $Command, [Parameter(Position=1)] [string] $Name = '.', [string] $Path = '.', [ValidateSet('ps1', 'bat', 'exe')] [string] $Target = 'ps1', [ValidateSet('Desktop', 'Core')] [string] $PSEdition = $PSVersionTable.PSEdition, [switch] $BundlePSCore, [switch] $HideConsole ) switch($Command){ 'new' { New-PSBundle -Path $Name } 'install' { Install-PSBundleModule -ModuleName $Name -Path $Path } 'build' { Build-PSBundle -Path $Name -Target $Target -PSEdition $PSEdition -BundlePSCore:$BundlePSCore.IsPresent -HideConsole:$HideConsole.IsPresent } } } # Export-ModuleMember -Function New-PSBundle,Install-PSBundleModule,Build-PSBundle,Invoke-PSBundle,New-BundledExe,Get-RandomString -Alias psbundle |