Skip to content

Commit f9260a0

Browse files
DenisNikulin5Tim BrighamDenis Nikulin
authored
Support for PowerShell CLM (#4923)
* Moving Vsts commands from CLI to dedicated PS script file --------- Co-authored-by: Tim Brigham <[email protected]> Co-authored-by: Denis Nikulin <[email protected]>
1 parent 18448fa commit f9260a0

File tree

3 files changed

+106
-1
lines changed

3 files changed

+106
-1
lines changed

src/Agent.Sdk/Knob/AgentKnobs.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -731,5 +731,11 @@ public class AgentKnobs
731731
"Show warning message on the OS which is not supported by .NET 8",
732732
new PipelineFeatureSource("Net8UnsupportedOsWarning"),
733733
new BuiltInDefaultKnobSource("true"));
734+
735+
public static readonly Knob UsePSScriptWrapper = new Knob(
736+
nameof(UsePSScriptWrapper),
737+
"Use PowerShell script wrapper to handle PowerShell ConstrainedLanguage mode.",
738+
new PipelineFeatureSource("UsePSScriptWrapper"),
739+
new BuiltInDefaultKnobSource("false"));
734740
}
735741
}

src/Agent.Worker/Handlers/PowerShell3Handler.cs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,11 +57,26 @@ public async Task RunAsync()
5757
ArgUtil.File(moduleFile, nameof(moduleFile));
5858

5959
// Craft the args to pass to PowerShell.exe.
60-
string powerShellExeArgs = StringUtil.Format(
60+
string powerShellExeArgs = string.Empty;
61+
62+
if (AgentKnobs.UsePSScriptWrapper.GetValue(ExecutionContext).AsBoolean())
63+
{
64+
powerShellExeArgs = StringUtil.Format(
65+
@"-NoLogo -Sta -NoProfile -ExecutionPolicy Unrestricted -Command ""{3}"" -VstsSdkPath {0} -DebugOption {1} -ScriptBlockString ""{2}""",
66+
StepHost.ResolvePathForStepHost(moduleFile).Replace("'", "''"), // nested within a single-quoted string module file name arg #0
67+
ExecutionContext.Variables.System_Debug == true ? "Continue" : "SilentlyContinue", // system debug status variable arg #1
68+
StepHost.ResolvePathForStepHost(scriptFile).Replace("'", "''''"), // nested within a single-quoted string within a single-quoted string arg #2
69+
Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Bin), "powershell", "Start-AzpTask.ps1") // path to wrapper script arg #3
70+
); // nested within a single-quoted string within a single-quoted string
71+
}
72+
else
73+
{
74+
powerShellExeArgs = StringUtil.Format(
6175
@"-NoLogo -Sta -NoProfile -NonInteractive -ExecutionPolicy Unrestricted -Command "". ([scriptblock]::Create('if ([Console]::InputEncoding -is [Text.UTF8Encoding] -and [Console]::InputEncoding.GetPreamble().Length -ne 0) {{ [Console]::InputEncoding = New-Object Text.UTF8Encoding $false }} if (!$PSHOME) {{ $null = Get-Item -LiteralPath ''variable:PSHOME'' }} else {{ Import-Module -Name ([System.IO.Path]::Combine($PSHOME, ''Modules\Microsoft.PowerShell.Management\Microsoft.PowerShell.Management.psd1'')) ; Import-Module -Name ([System.IO.Path]::Combine($PSHOME, ''Modules\Microsoft.PowerShell.Utility\Microsoft.PowerShell.Utility.psd1'')) }}')) 2>&1 | ForEach-Object {{ Write-Verbose $_.Exception.Message -Verbose }} ; Import-Module -Name '{0}' -ArgumentList @{{ NonInteractive = $true }} -ErrorAction Stop ; $VerbosePreference = '{1}' ; $DebugPreference = '{1}' ; Invoke-VstsTaskScript -ScriptBlock ([scriptblock]::Create('. ''{2}'''))""",
6276
StepHost.ResolvePathForStepHost(moduleFile).Replace("'", "''"), // nested within a single-quoted string
6377
ExecutionContext.Variables.System_Debug == true ? "Continue" : "SilentlyContinue",
6478
StepHost.ResolvePathForStepHost(scriptFile).Replace("'", "''''")); // nested within a single-quoted string within a single-quoted string
79+
}
6580

6681
// Resolve powershell.exe.
6782
string powerShellExe = "powershell.exe";
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
<#
2+
A PowerShell script that is used to invoke a VSTS task script. This script is used by the VSTS task runner to invoke the task script.
3+
This script replaces some legacy stuff in PowerShell3Handler.cs and turns it into a dedicated signed script.
4+
since it is parameterized it can be signed and trusted for WDAC and CLM.
5+
#>
6+
7+
param (
8+
[Parameter(mandatory = $true)]
9+
[string]$VstsSdkPath,
10+
11+
[Parameter(mandatory = $true)]
12+
[string]$DebugOption,
13+
14+
[Parameter(mandatory = $true)]
15+
[string]$ScriptBlockString
16+
17+
)
18+
19+
function Get-ClmStatus {
20+
# This is new functionality to detect if we are running in a constrained language mode.
21+
# This is only used to display debug data if the device is in CLM mode by default.
22+
23+
# Create a temp file and add the command which not allowed in constrained language mode.
24+
$tempFileGuid = New-Guid | Select-Object -Expand Guid
25+
$tempFile = "$($env:AGENT_TEMPDIRECTORY)\$($tempFileGuid).ps1"
26+
27+
Write-Output '$null = New-Object -TypeName System.Collections.ArrayList' | Out-File -FilePath $tempFile
28+
29+
try {
30+
. $tempFile
31+
$status = "FullLanguage"
32+
}
33+
catch [System.Management.Automation.PSNotSupportedException] {
34+
$status = "ConstrainedLanguage"
35+
}
36+
37+
Remove-Item $tempFile
38+
return $status
39+
}
40+
41+
$VerbosePreference = $DebugOption
42+
$DebugPreference = $DebugOption
43+
44+
if (!$PSHOME) {
45+
Write-Error -Message "The execution cannot be continued since the PSHOME variable is not defined." -ErrorAction Stop
46+
}
47+
48+
# Check if the device is in CLM mode by default.
49+
$clmResults = Get-ClmStatus
50+
Write-Verbose "PowerShell Language mode: $($clmResults)"
51+
52+
if ([Console]::InputEncoding -is [Text.UTF8Encoding] -and [Console]::InputEncoding.GetPreamble().Length -ne 0) {
53+
[Console]::InputEncoding = New-Object Text.UTF8Encoding $false
54+
}
55+
56+
Import-Module -Name ([System.IO.Path]::Combine($PSHOME, 'Modules\Microsoft.PowerShell.Management\Microsoft.PowerShell.Management.psd1'))
57+
Import-Module -Name ([System.IO.Path]::Combine($PSHOME, 'Modules\Microsoft.PowerShell.Utility\Microsoft.PowerShell.Utility.psd1'))
58+
59+
$importSplat = @{
60+
Name = $VstsSdkPath
61+
ErrorAction = 'Stop'
62+
}
63+
64+
# Import the module and catch any errors
65+
try {
66+
Import-Module @importSplat
67+
}
68+
catch {
69+
Write-Verbose $_.Exception.Message -Verbose
70+
throw $_.Exception
71+
}
72+
73+
# Now create the task and hand of to the task script
74+
try {
75+
Invoke-VstsTaskScript -ScriptBlock ([scriptblock]::Create( $ScriptBlockString ))
76+
}
77+
# We want to add improved error handling here - if the error is "xxx\powershell.ps1 is not recognized as the name of a cmdlet, function, script file, or operable program"
78+
#
79+
catch {
80+
Write-Verbose "Invoke-VstsTaskScript -ScriptBlock ([scriptblock]::Create( $ScriptBlockString ))"
81+
Write-Verbose $_.Exception.Message -Verbose
82+
throw $_.Exception
83+
}
84+
#

0 commit comments

Comments
 (0)