1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125
|
# (c) 2025 Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
#AnsibleRequires -CSharpUtil Ansible.Become
using namespace System.Collections
using namespace System.Diagnostics
using namespace System.IO
using namespace System.Management.Automation
using namespace System.Management.Automation.Security
using namespace System.Net
using namespace System.Text
[CmdletBinding()]
param (
[Parameter()]
[AllowEmptyString()]
[string]
$BecomeUser,
[Parameter()]
[SecureString]
$BecomePassword,
[Parameter()]
[string]
$LogonType = 'Interactive',
[Parameter()]
[string]
$LogonFlags = 'WithProfile'
)
Import-CSharpUtil -Name 'Ansible.AccessToken.cs', 'Ansible.Become.cs', 'Ansible.Process.cs'
# We need to set password to the value of NullString so a null password is
# preserved when crossing the .NET boundary. If we pass $null it will
# automatically be converted to "" and we need to keep the distinction for
# accounts that don't have a password and when someone wants to become without
# knowing the password.
$password = [NullString]::Value
if ($null -ne $BecomePassword) {
$password = [NetworkCredential]::new("", $BecomePassword).Password
}
$executable = if ($PSVersionTable.PSVersion -lt '6.0') {
'powershell.exe'
}
else {
'pwsh.exe'
}
$executablePath = Join-Path -Path $PSHome -ChildPath $executable
$actionInfo = Get-AnsibleExecWrapper -EncodeInputOutput
$bootstrapManifest = ConvertTo-Json -InputObject @{
n = "exec_wrapper-become-$([Guid]::NewGuid()).ps1"
s = $actionInfo.ScriptInfo.Script
p = $actionInfo.Parameters
} -Depth 99 -Compress
# NB: CreateProcessWithTokenW commandline maxes out at 1024 chars, must
# bootstrap via small wrapper to invoke the exec_wrapper. Strings are used to
# avoid sanity tests like aliases and spaces.
[string]$command = @'
$m=foreach($i in $input){
if([string]::Equals($i,"`0`0`0`0")){break}
$i
}
$m=$m|ConvertFrom-Json
$p=@{}
foreach($o in $m.p.PSObject.Properties){$p[$o.Name]=$o.Value}
'@
if ([SystemPolicy]::GetSystemLockdownPolicy() -eq 'Enforce') {
# If we started in CLM we need to execute the script from a file so that
# PowerShell validates our exec_wrapper is trusted and will run in FLM.
$command += @'
$n=Join-Path $env:TEMP $m.n
$null=New-Item $n -Value $m.s -Type File -Force
try{$input|&$n @p}
finally{if(Test-Path -LiteralPath $n){Remove-Item -LiteralPath $n -Force}}
'@
}
else {
# If we started in FLM we pass the script through stdin and execute in
# memory.
$command += @'
$c=[System.Management.Automation.Language.Parser]::ParseInput($m.s,$m.n,[ref]$null,[ref]$null).GetScriptBlock()
$input|&$c @p
'@
}
# Strip out any leading or trailing whitespace and remove empty lines.
$command = @(
($command -split "\r?\n") |
ForEach-Object { $_.Trim() } |
Where-Object { -not [string]::IsNullOrWhiteSpace($_) }
) -join "`n"
$encCommand = [Convert]::ToBase64String([Encoding]::Unicode.GetBytes($command))
# Shortened version of '-NonInteractive -NoProfile -ExecutionPolicy Bypass -EncodedCommand $encCommand'
$commandLine = "$executable -noni -nop -ex Bypass -e $encCommand"
$result = [Ansible.Become.BecomeUtil]::CreateProcessAsUser(
$BecomeUser,
$password,
$LogonFlags,
$LogonType,
$executablePath,
$commandLine,
$env:SystemRoot,
$null,
"$bootstrapManifest`n`0`0`0`0`n$($actionInfo.InputData)")
$stdout = $result.StandardOut
try {
$stdout = [Encoding]::UTF8.GetString([Convert]::FromBase64String($stdout))
}
catch [FormatException] {
# output wasn't Base64, ignore as it may contain an error message we want to pass to Ansible
$null = $_
}
$Host.UI.WriteLine($stdout)
Write-PowerShellClixmlStderr -Output $result.StandardError
$Host.SetShouldExit($result.ExitCode)
|