diff --git a/assets/GroupPolicy/InstallPSCorePolicyDefinitions.ps1 b/assets/GroupPolicy/InstallPSCorePolicyDefinitions.ps1
new file mode 100644
index 0000000000..9634059bec
--- /dev/null
+++ b/assets/GroupPolicy/InstallPSCorePolicyDefinitions.ps1
@@ -0,0 +1,88 @@
+# Copyright (c) Microsoft Corporation.
+# Licensed under the MIT License.
+
+<#
+.Synopsis
+ Group Policy tools use administrative template files (.admx, .adml) to populate policy settings in the user interface.
+ This allows administrators to manage registry-based policy settings.
+ This script installes PowerShell Core Administrative Templates for Windows.
+.Notes
+ The PowerShellCoreExecutionPolicy.admx and PowerShellCoreExecutionPolicy.adml files are
+ expected to be at the location specified by the Path parameter with default value of the location of this script.
+#>
+[CmdletBinding()]
+param
+(
+ [ValidateNotNullOrEmpty()]
+ [string] $Path = $PSScriptRoot
+)
+Set-StrictMode -Version Latest
+$ErrorActionPreference = 'Stop'
+
+function Test-Elevated
+{
+ [CmdletBinding()]
+ [OutputType([bool])]
+ Param()
+
+ # if the current Powershell session was called with administrator privileges,
+ # the Administrator Group's well-known SID will show up in the Groups for the current identity.
+ # Note that the SID won't show up unless the process is elevated.
+ return (([Security.Principal.WindowsIdentity]::GetCurrent()).Groups -contains "S-1-5-32-544")
+}
+$IsWindowsOs = $PSHOME.EndsWith('\WindowsPowerShell\v1.0', [System.StringComparison]::OrdinalIgnoreCase) -or $IsWindows
+
+if (-not $IsWindowsOs)
+{
+ throw 'This script must be run on Windows.'
+}
+
+if (-not (Test-Elevated))
+{
+ throw 'This script must be run from an elevated process.'
+}
+
+if ([System.Management.Automation.Platform]::IsNanoServer)
+{
+ throw 'Group policy definitions are not supported on Nano Server.'
+}
+
+$admxName = 'PowerShellCoreExecutionPolicy.admx'
+$admlName = 'PowerShellCoreExecutionPolicy.adml'
+$admx = Get-Item -Path (Join-Path -Path $Path -ChildPath $admxName)
+$adml = Get-Item -Path (Join-Path -Path $Path -ChildPath $admlName)
+$admxTargetPath = Join-Path -Path $env:WINDIR -ChildPath "PolicyDefinitions"
+$admlTargetPath = Join-Path -Path $admxTargetPath -ChildPath "en-US"
+
+$files = @($admx, $adml)
+foreach ($file in $files)
+{
+ if (-not (Test-Path -Path $file))
+ {
+ throw "Could not find $($file.Name) at $Path"
+ }
+}
+
+Write-Verbose "Copying $admx to $admxTargetPath"
+Copy-Item -Path $admx -Destination $admxTargetPath -Force
+$admxTargetFullPath = Join-Path -Path $admxTargetPath -ChildPath $admxName
+if (Test-Path -Path $admxTargetFullPath)
+{
+ Write-Verbose "$admxName was installed successfully"
+}
+else
+{
+ Write-Error "Could not install $admxName"
+}
+
+Write-Verbose "Copying $adml to $admlTargetPath"
+Copy-Item -Path $adml -Destination $admlTargetPath -Force
+$admlTargetFullPath = Join-Path -Path $admlTargetPath -ChildPath $admlName
+if (Test-Path -Path $admlTargetFullPath)
+{
+ Write-Verbose "$admlName was installed successfully"
+}
+else
+{
+ Write-Error "Could not install $admlName"
+}
diff --git a/assets/GroupPolicy/PowerShellCoreExecutionPolicy.adml b/assets/GroupPolicy/PowerShellCoreExecutionPolicy.adml
new file mode 100644
index 0000000000..3068ae57a2
--- /dev/null
+++ b/assets/GroupPolicy/PowerShellCoreExecutionPolicy.adml
@@ -0,0 +1,125 @@
+
+
+ PowerShell Core
+ This file contains the configuration options for PowerShell Core
+
+
+ Allow all scripts
+ Allow only signed scripts
+ Turn on Script Execution
+ This policy setting lets you configure the script execution policy, controlling which scripts are allowed to run.
+
+If you enable this policy setting, the scripts selected in the drop-down list are allowed to run.
+
+The "Allow only signed scripts" policy setting allows scripts to execute only if they are signed by a trusted publisher.
+
+The "Allow local scripts and remote signed scripts" policy setting allows any local scrips to run; scripts that originate from the internet must be signed by a trusted publisher.
+
+The "Allow all scripts" policy setting allows all scripts to run.
+
+If you disable this policy setting, no scripts are allowed to run.
+
+Note: This policy setting exists under both "Computer Configuration" and "User Configuration" in the Local Group Policy Editor. The "Computer Configuration" has precedence over "User Configuration."
+
+If you disable or do not configure this policy setting, it reverts to a per-machine preference setting; the default if that is not configured is "Allow local scripts and remote signed scripts."
+ PowerShell Core
+ Allow local scripts and remote signed scripts
+ At least Microsoft Windows 7 or Windows Server 2008 family
+
+ Turn on Module Logging
+
+ This policy setting allows you to turn on logging for PowerShell Core modules.
+
+ If you enable this policy setting, pipeline execution events for members of the specified modules are recorded in the PowerShell Core log in Event Viewer. Enabling this policy setting for a module is equivalent to setting the LogPipelineExecutionDetails property of the module to True.
+
+ If you disable this policy setting, logging of execution events is disabled for all PowerShell Core modules. Disabling this policy setting for a module is equivalent to setting the LogPipelineExecutionDetails property of the module to False.
+
+ If this policy setting is not configured, the LogPipelineExecutionDetails property of a module determines whether the execution events of a module are logged. By default, the LogPipelineExecutionDetails property of all modules is set to False.
+
+ To add modules to the policy setting list, click Show, and then type the module names in the list. The modules in the list must be installed on the computer.
+
+ Note: This policy setting exists under both Computer Configuration and User Configuration in the Group Policy Editor. The Computer Configuration policy setting takes precedence over the User Configuration policy setting.
+
+
+ Turn on PowerShell Transcription
+
+ This policy setting lets you capture the input and output of PowerShell Core commands into text-based transcripts.
+
+ If you enable this policy setting, PowerShell Core will enable transcription logging for PowerShell Core and any other
+ applications that leverage the PowerShell Core engine. By default, PowerShell Core will record transcript output to each users' My Documents
+ directory, with a file name that includes 'PowerShell_transcript', along with the computer name and time started. Enabling this policy is equivalent
+ to calling the Start-Transcript cmdlet on each PowerShell Core session.
+
+ If you disable this policy setting, transcription logging of PowerShell-based applications is disabled by default, although transcripting can still be enabled
+ through the Start-Transcript cmdlet.
+
+ If you use the OutputDirectory setting to enable transcription logging to a shared location, be sure to limit access to that directory to prevent users
+ from viewing the transcripts of other users or computers.
+
+ Note: This policy setting exists under both Computer Configuration and User Configuration in the Group Policy Editor. The Computer Configuration policy setting takes precedence over the User Configuration policy setting.
+
+
+ Turn on PowerShell Script Block Logging
+
+ This policy setting enables logging of all PowerShell script input to the Microsoft-Windows-PowerShell/Operational event log. If you enable this policy setting,
+ PowerShell Core will log the processing of commands, script blocks, functions, and scripts - whether invoked interactively, or through automation.
+
+ If you disable this policy setting, logging of PowerShell script input is disabled.
+
+ If you enable the Script Block Invocation Logging, PowerShell additionally logs events when invocation of a command, script block, function, or script
+ starts or stops. Enabling Invocation Logging generates a high volume of event logs.
+
+ Note: This policy setting exists under both Computer Configuration and User Configuration in the Group Policy Editor. The Computer Configuration policy setting takes precedence over the User Configuration policy setting.
+
+
+ Set the default source path for Update-Help
+ This policy setting allows you to set the default value of the SourcePath parameter on the Update-Help cmdlet.
+
+If you enable this policy setting, the Update-Help cmdlet will use the specified value as the default value for the SourcePath parameter. This default value can be overridden by specifying a different value with the SourcePath parameter on the Update-Help cmdlet.
+
+If this policy setting is disabled or not configured, this policy setting does not set a default value for the SourcePath parameter of the Update-Help cmdlet.
+
+Note: This policy setting exists under both Computer Configuration and User Configuration in the Group Policy Editor. The Computer Configuration policy setting takes precedence over the User Configuration policy setting.
+
+ Console session configuration
+ Specifies a configuration endpoint in which PowerShell is run. This can be any endpoint registered on the local machine including the default PowerShell remoting endpoints or a custom endpoint having specific user role capabilities.
+
+
+
+
+
+ Use Windows PowerShell Policy setting.
+ Execution Policy
+
+
+ Use Windows PowerShell Policy setting.
+ To turn on logging for one or more modules, click Show, and then type the module names in the list. Wildcards are supported.
+ Module Names
+ To turn on logging for the PowerShell Core core modules, type the following module names in the list:
+ Microsoft.PowerShell.*
+ Microsoft.WSMan.Management
+
+
+ Use Windows PowerShell Policy setting.
+
+ Include invocation headers:
+
+
+ Use Windows PowerShell Policy setting.
+ Log script block invocation start / stop events:
+
+
+ Use Windows PowerShell Policy setting.
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/assets/GroupPolicy/PowerShellCoreExecutionPolicy.admx b/assets/GroupPolicy/PowerShellCoreExecutionPolicy.admx
new file mode 100644
index 0000000000..55e888fdfe
--- /dev/null
+++ b/assets/GroupPolicy/PowerShellCoreExecutionPolicy.admx
@@ -0,0 +1,119 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ AllSigned
+
+
+
+
+ RemoteSigned
+
+
+
+
+ Unrestricted
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/assets/files.wxs b/assets/files.wxs
index 45cccee967..78214c2181 100644
--- a/assets/files.wxs
+++ b/assets/files.wxs
@@ -3051,6 +3051,15 @@
+
+
+
+
+
+
+
+
+
@@ -3888,6 +3897,9 @@
+
+
+
diff --git a/src/System.Management.Automation/engine/Utils.cs b/src/System.Management.Automation/engine/Utils.cs
index 829870c692..d20aebc88e 100644
--- a/src/System.Management.Automation/engine/Utils.cs
+++ b/src/System.Management.Automation/engine/Utils.cs
@@ -807,40 +807,43 @@ namespace System.Management.Automation
{nameof(ConsoleSessionConfiguration), @"Software\Policies\Microsoft\PowerShellCore\ConsoleSessionConfiguration"}
};
+ private static readonly Dictionary WindowsPowershellGroupPolicyKeys = new Dictionary
+ {
+ {nameof(ScriptExecution), @"Software\Policies\Microsoft\Windows\PowerShell"},
+ {nameof(ScriptBlockLogging), @"Software\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging"},
+ {nameof(ModuleLogging), @"Software\Policies\Microsoft\Windows\PowerShell\ModuleLogging"},
+ {nameof(Transcription), @"Software\Policies\Microsoft\Windows\PowerShell\Transcription"},
+ {nameof(UpdatableHelp), @"Software\Policies\Microsoft\Windows\PowerShell\UpdatableHelp"},
+ };
+
+ private const string PolicySettingFallbackKey = "UseWindowsPowerShellPolicySetting";
+
private static readonly ConcurrentDictionary> s_cachedPoliciesFromRegistry =
new ConcurrentDictionary>();
private static readonly Func> s_subCacheCreationDelegate =
- key => new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase);
+ key => new ConcurrentDictionary(StringComparer.Ordinal);
///
- /// The implementation of fetching a specific kind of policy setting from the given configuration scope.
+ /// Read policy settings from a registry key into a policy object.
///
- private static T GetPolicySettingFromGPOImpl(ConfigScope scope) where T : PolicyBase, new()
+ /// Policy object that will be filled with values from registry.
+ /// Type of policy object used.
+ /// Registry key that has policy settings.
+ /// True if any property was successfully set on the policy object.
+ private static bool TrySetPolicySettingsFromRegistryKey(object instance, Type instanceType, RegistryKey gpoKey)
{
- Type tType = typeof(T);
- // SystemWide scope means 'LocalMachine' root key when query from registry
- RegistryKey rootKey = (scope == ConfigScope.AllUsers) ? Registry.LocalMachine : Registry.CurrentUser;
+ var properties = instanceType.GetProperties(BindingFlags.Instance | BindingFlags.Public);
+ bool isAnyPropertySet = false;
- GroupPolicyKeys.TryGetValue(tType.Name, out string gpoKeyPath);
- Diagnostics.Assert(gpoKeyPath != null, StringUtil.Format("The GPO registry key path should be pre-defined for {0}", tType.Name));
+ string[] valueNames = gpoKey.GetValueNames();
+ string[] subKeyNames = gpoKey.GetSubKeyNames();
+ var valueNameSet = valueNames.Length > 0 ? new HashSet(valueNames, StringComparer.OrdinalIgnoreCase) : null;
+ var subKeyNameSet = subKeyNames.Length > 0 ? new HashSet(subKeyNames, StringComparer.OrdinalIgnoreCase) : null;
- using (RegistryKey gpoKey = rootKey.OpenSubKey(gpoKeyPath))
+ // If there are any values or subkeys in the registry key - read them into the policy instance object
+ if ((valueNameSet != null) || (subKeyNameSet != null))
{
- // If the corresponding GPO key doesn't exist, return null
- if (gpoKey == null) { return null; }
-
- // The corresponding GPO key exists, then create an instance of T
- // and populate its properties with the settings
- object tInstance = Activator.CreateInstance(tType, nonPublic: true);
- var properties = tType.GetProperties(BindingFlags.Instance | BindingFlags.Public);
- bool isAnyPropertySet = false;
-
- string[] valueNames = gpoKey.GetValueNames();
- string[] subKeyNames = gpoKey.GetSubKeyNames();
- var valueNameSet = valueNames.Length > 0 ? new HashSet(valueNames, StringComparer.OrdinalIgnoreCase) : null;
- var subKeyNameSet = subKeyNames.Length > 0 ? new HashSet(subKeyNames, StringComparer.OrdinalIgnoreCase) : null;
-
foreach (var property in properties)
{
string settingName = property.Name;
@@ -895,18 +898,61 @@ namespace System.Management.Automation
break;
default:
- Diagnostics.Assert(false, "Should be unreachable code. Update this switch block when properties of new types are added to PowerShell policy types.");
- break;
+ throw System.Management.Automation.Interpreter.Assert.Unreachable;
}
// Set the property if the value is not null
if (propertyValue != null)
{
- property.SetValue(tInstance, propertyValue);
+ property.SetValue(instance, propertyValue);
isAnyPropertySet = true;
}
}
}
+ }
+
+ return isAnyPropertySet;
+ }
+
+ ///
+ /// The implementation of fetching a specific kind of policy setting from the given configuration scope.
+ ///
+ private static T GetPolicySettingFromGPOImpl(ConfigScope scope) where T : PolicyBase, new()
+ {
+ Type tType = typeof(T);
+ // SystemWide scope means 'LocalMachine' root key when query from registry
+ RegistryKey rootKey = (scope == ConfigScope.AllUsers) ? Registry.LocalMachine : Registry.CurrentUser;
+
+ GroupPolicyKeys.TryGetValue(tType.Name, out string gpoKeyPath);
+ Diagnostics.Assert(gpoKeyPath != null, StringUtil.Format("The GPO registry key path should be pre-defined for {0}", tType.Name));
+
+ using (RegistryKey gpoKey = rootKey.OpenSubKey(gpoKeyPath))
+ {
+ // If the corresponding GPO key doesn't exist, return null
+ if (gpoKey == null) { return null; }
+
+ // The corresponding GPO key exists, then create an instance of T
+ // and populate its properties with the settings
+ object tInstance = Activator.CreateInstance(tType, nonPublic: true);
+ bool isAnyPropertySet = false;
+
+ // if PolicySettingFallbackKey is Not set - use PowerShell Core policy reg key
+ if ((int)gpoKey.GetValue(PolicySettingFallbackKey, 0) == 0)
+ {
+ isAnyPropertySet = TrySetPolicySettingsFromRegistryKey(tInstance, tType, gpoKey);
+ }
+ else
+ {
+ // when PolicySettingFallbackKey flag is set (REG_DWORD "1") use Windows PS policy reg key
+ WindowsPowershellGroupPolicyKeys.TryGetValue(tType.Name, out string winPowershellGpoKeyPath);
+ Diagnostics.Assert(winPowershellGpoKeyPath != null, StringUtil.Format("The Windows PS GPO registry key path should be pre-defined for {0}", tType.Name));
+ using (RegistryKey winPowershellGpoKey = rootKey.OpenSubKey(winPowershellGpoKeyPath))
+ {
+ // If the corresponding Windows PS GPO key doesn't exist, return null
+ if (winPowershellGpoKey == null) { return null; }
+ isAnyPropertySet = TrySetPolicySettingsFromRegistryKey(tInstance, tType, winPowershellGpoKey);
+ }
+ }
// If no property is set, then we consider this policy as undefined
return isAnyPropertySet ? (T)tInstance : null;
diff --git a/src/powershell-win-core/powershell-win-core.csproj b/src/powershell-win-core/powershell-win-core.csproj
index f1ca7c18ac..e17f8cf228 100644
--- a/src/powershell-win-core/powershell-win-core.csproj
+++ b/src/powershell-win-core/powershell-win-core.csproj
@@ -35,7 +35,7 @@
PreserveNewestPreserveNewest
-
+ PreserveNewestPreserveNewest
diff --git a/test/powershell/engine/Basic/GroupPolicySettings.Tests.ps1 b/test/powershell/engine/Basic/GroupPolicySettings.Tests.ps1
new file mode 100644
index 0000000000..e84ee94ca3
--- /dev/null
+++ b/test/powershell/engine/Basic/GroupPolicySettings.Tests.ps1
@@ -0,0 +1,248 @@
+# Copyright (c) Microsoft Corporation.
+# Licensed under the MIT License.
+
+Describe 'Group policy settings tests' -Tag CI,RequireAdminOnWindows {
+ BeforeAll {
+ $originalDefaultParameterValues = $PSDefaultParameterValues.Clone()
+ if ( ! $IsWindows ) {
+ $PSDefaultParameterValues["it:skip"] = $true
+ }
+ else {
+ [System.Management.Automation.Internal.InternalTestHooks]::SetTestHook('BypassGroupPolicyCaching', $True)
+ }
+ }
+ AfterAll {
+ $global:PSDefaultParameterValues = $originalDefaultParameterValues
+ if ( $IsWindows ) {
+ [System.Management.Automation.Internal.InternalTestHooks]::SetTestHook('BypassGroupPolicyCaching', $False)
+ }
+ }
+
+ Context 'Group policy settings tests' {
+
+ BeforeEach {
+ $KeyRoot = 'HKCU:\Software\Policies\Microsoft\PowerShellCore'
+ if (-not (Test-Path $KeyRoot)) {$null = New-Item $KeyRoot}
+
+ $WinPSKeyRoot = 'HKCU:\Software\Policies\Microsoft\Windows\PowerShell'
+ if (-not (Test-Path $WinPSKeyRoot)) {$null = New-Item $WinPSKeyRoot}
+ }
+
+ AfterEach {
+ Remove-item $KeyRoot -Recurse -Force > $null
+ Remove-item $WinPSKeyRoot -Recurse -Force > $null
+ }
+
+ It 'Execution policy test' {
+ function TestFeature
+ {
+ param([string]$KeyPath)
+
+ Set-ItemProperty -Path $KeyPath -Name EnableScripts -Value 1 -Force
+
+ Set-ItemProperty -Path $KeyPath -Name ExecutionPolicy -Value 'Unrestricted' -Force
+ (Get-ExecutionPolicy) | Should -Be 'Unrestricted'
+ Set-ItemProperty -Path $KeyPath -Name ExecutionPolicy -Value 'AllSigned' -Force
+ (Get-ExecutionPolicy) | Should -Be 'AllSigned'
+ Set-ItemProperty -Path $KeyPath -Name ExecutionPolicy -Value 'RemoteSigned' -Force
+ (Get-ExecutionPolicy) | Should -Be 'RemoteSigned'
+
+ Remove-ItemProperty -Path $KeyPath -Name ExecutionPolicy -Force
+ }
+
+ TestFeature -KeyPath $KeyRoot
+
+ Set-ItemProperty -Path $KeyRoot -Name UseWindowsPowerShellPolicySetting -Value 1 -Force
+ TestFeature -KeyPath $WinPSKeyRoot
+ }
+
+ It 'Module logging policy test' {
+ function TestFeature
+ {
+ param([string]$KeyPath)
+
+ $ModuleToLog = 'Microsoft.PowerShell.Utility'
+ $ModuleNamesKeyPath = Join-Path $KeyPath 'ModuleNames'
+ if (-not (Test-Path $ModuleNamesKeyPath)) {$null = New-Item $ModuleNamesKeyPath}
+
+ Remove-Module $ModuleToLog -ErrorAction SilentlyContinue
+ Import-Module $ModuleToLog
+ (Get-Module $ModuleToLog).LogPipelineExecutionDetails | Should -Be $False # without GP logging for the module should be OFF
+
+ # enable GP
+ [string]$RareCommand = Get-Random
+ Set-ItemProperty -Path $KeyPath -Name EnableModuleLogging -Value 1 -Force
+ Set-ItemProperty -Path $ModuleNamesKeyPath -Name $ModuleToLog -Value $ModuleToLog -Force
+
+ Remove-Module $ModuleToLog -ErrorAction SilentlyContinue
+ Import-Module $ModuleToLog # this will read and start using GP setting
+ (Get-Module $ModuleToLog).LogPipelineExecutionDetails | Should -Be $True # with GP logging for the module should be ON
+
+ Get-Alias $RareCommand -ErrorAction SilentlyContinue | Out-Null
+
+ (Get-Module $ModuleToLog).LogPipelineExecutionDetails = $False # turn off logging
+ Remove-ItemProperty -Path $KeyPath -Name EnableModuleLogging -Force # turn off GP setting
+ Remove-item $ModuleNamesKeyPath -Recurse -Force
+ # usually event becomes visible in the log after ~500 ms
+ # set timeout for 5 seconds
+ Wait-UntilTrue -sb { Get-WinEvent -FilterHashtable @{ ProviderName="PowerShellCore"; Id = 4103 } -MaxEvents 5 | ? {$_.Message.Contains($RareCommand)} } -TimeoutInMilliseconds (5*1000) -IntervalInMilliseconds 100 | Should -BeTrue
+ }
+
+ $KeyPath = Join-Path $KeyRoot 'ModuleLogging'
+ if (-not (Test-Path $KeyPath)) {$null = New-Item $KeyPath}
+
+ TestFeature -KeyPath $KeyPath
+
+ Set-ItemProperty -Path $KeyPath -Name UseWindowsPowerShellPolicySetting -Value 1 -Force
+ $WinKeyPath = Join-Path $WinPSKeyRoot 'ModuleLogging'
+ if (-not (Test-Path $WinKeyPath)) {$null = New-Item $WinKeyPath}
+
+ TestFeature -KeyPath $WinKeyPath
+ }
+
+ It 'ScriptBlock logging policy test' {
+ function TestFeature
+ {
+ param([string]$KeyPath)
+
+ [string]$RareCommand = Get-Random
+ Set-ItemProperty -Path $KeyPath -Name EnableScriptBlockLogging -Value 1 -Force
+ Set-ItemProperty -Path $KeyPath -Name EnableScriptBlockInvocationLogging -Value 1 -Force
+ Invoke-Expression "$RareCommand | Out-Null"
+ Remove-ItemProperty -Path $KeyPath -Name EnableScriptBlockLogging -Force
+ Remove-ItemProperty -Path $KeyPath -Name EnableScriptBlockInvocationLogging -Force
+ # usually event becomes visible in the log after ~500 ms
+ # set timeout for 5 seconds
+ Wait-UntilTrue -sb { $script:CreatingScriptblockEvent = Get-WinEvent -FilterHashtable @{ ProviderName="PowerShellCore"; Id = 4104 } -MaxEvents 5 | ? {$_.Message.Contains($RareCommand)}; $script:CreatingScriptblockEvent } -TimeoutInMilliseconds (5*1000) -IntervalInMilliseconds 100 | Should -BeTrue
+
+ $sbStringStart = $script:CreatingScriptblockEvent.Message.IndexOf('ScriptBlock ID:')
+ $sbStringEnd = $script:CreatingScriptblockEvent.Message.IndexOf(0x0D, $sbStringStart)
+ $sbString = $script:CreatingScriptblockEvent.Message.Substring($sbStringStart, $sbStringEnd - $sbStringStart)
+
+ $StartedScriptBlockInvocationEvent = Get-WinEvent -FilterHashtable @{ ProviderName="PowerShellCore"; Id = 4105 } -MaxEvents 5 | ? {$_.Message.Contains($sbString)}
+ $StartedScriptBlockInvocationEvent | Should Not BeNullOrEmpty
+ $CompletedScriptBlockInvocationEvent = Get-WinEvent -FilterHashtable @{ ProviderName="PowerShellCore"; Id = 4106 } -MaxEvents 5 | ? {$_.Message.Contains($sbString)}
+ $CompletedScriptBlockInvocationEvent | Should Not BeNullOrEmpty
+ }
+
+ $KeyPath = Join-Path $KeyRoot 'ScriptBlockLogging'
+ if (-not (Test-Path $KeyPath)) {$null = New-Item $KeyPath}
+
+ TestFeature -KeyPath $KeyPath
+
+ Set-ItemProperty -Path $KeyPath -Name UseWindowsPowerShellPolicySetting -Value 1 -Force
+ $WinKeyPath = Join-Path $WinPSKeyRoot 'ScriptBlockLogging'
+ if (-not (Test-Path $WinKeyPath)) {$null = New-Item $WinKeyPath}
+
+ TestFeature -KeyPath $WinKeyPath
+ }
+
+ It 'Transcription policy test' {
+
+ function TestFeature
+ {
+ param([string]$KeyPath)
+
+ $OutputDirectory = Join-path $([System.IO.Path]::GetTempPath()) $(Get-Random)
+ $null = New-Item -Type Directory -Path $OutputDirectory -Force
+
+ Set-ItemProperty -Path $KeyPath -Name EnableTranscripting -Value 1 -Force
+ Set-ItemProperty -Path $KeyPath -Name OutputDirectory -Value $OutputDirectory -Force
+ Set-ItemProperty -Path $KeyPath -Name EnableInvocationHeader -Value 1 -Force
+
+ $number = get-random
+ $null = pwsh -NoProfile -NonInteractive -c "$number"
+
+ Remove-ItemProperty -Path $KeyPath -Name OutputDirectory -Force
+ Remove-ItemProperty -Path $KeyPath -Name EnableInvocationHeader -Force
+
+ $LogPath = (gci -Path $OutputDirectory -Filter "PowerShell_transcript*.txt" -Recurse).FullName
+ $Log = Get-Content $LogPath -Raw
+
+ $Log.Contains("$number") | should be $True # verifies that Transcription policy works
+ $Log.Contains("Command start time:") | should be $True # verifies that EnableInvocationHeader works
+
+ Remove-Item -Path $OutputDirectory -Recurse -Force
+ }
+
+ $KeyPath = Join-Path $KeyRoot 'Transcription'
+ if (-not (Test-Path $KeyPath)) {$null = New-Item $KeyPath}
+
+ TestFeature -KeyPath $KeyPath
+
+ Set-ItemProperty -Path $KeyPath -Name UseWindowsPowerShellPolicySetting -Value 1 -Force
+ $WinKeyPath = Join-Path $WinPSKeyRoot 'Transcription'
+ if (-not (Test-Path $WinKeyPath)) {$null = New-Item $WinKeyPath}
+
+ TestFeature -KeyPath $WinKeyPath
+ }
+
+ It 'Default SourcePath on Update-Help policy test' {
+ function TestFeature
+ {
+ param([string]$KeyPath)
+
+ $HelpPath = Join-path 'TestDrive:\' $(Get-Random)
+ $null = New-Item -Type Directory -Path $HelpPath -ErrorAction SilentlyContinue
+ $ModuleName = 'Microsoft.PowerShell.Utility'
+ Save-Help -Module $ModuleName -DestinationPath $HelpPath -Force
+
+ Set-ItemProperty -Path $KeyPath -Name EnableUpdateHelpDefaultSourcePath -Value 1 -Force
+ Set-ItemProperty -Path $KeyPath -Name DefaultSourcePath -Value $HelpPath -Force
+
+ # this should throw error cause we didn't save the help for this module locally;
+ # this ensures that Update-Help is not going to Internet to download help
+ { Update-Help -Module Microsoft.PowerShell.Management -Force -ErrorAction Stop } | Should -Throw -ErrorId "UnableToRetrieveHelpInfoXml,Microsoft.PowerShell.Commands.UpdateHelpCommand"
+
+ # this should use saved help in location specified in the policy and should NOT throw error
+ Update-Help -Module Microsoft.PowerShell.Utility -Force
+ }
+
+ $HKLM_KeyRoot = 'HKLM:\Software\Policies\Microsoft\PowerShellCore'
+ if (-not (Test-Path $HKLM_KeyRoot)) {$null = New-Item $HKLM_KeyRoot}
+ $KeyPath = Join-Path $HKLM_KeyRoot 'UpdatableHelp'
+ if (-not (Test-Path $KeyPath)) {$null = New-Item $KeyPath}
+
+ TestFeature -KeyPath $KeyPath
+
+ Set-ItemProperty -Path $KeyPath -Name UseWindowsPowerShellPolicySetting -Value 1 -Force
+ $HKLM_WinPSKeyRoot = 'HKLM:\Software\Policies\Microsoft\Windows\PowerShell'
+ if (-not (Test-Path $HKLM_WinPSKeyRoot)) {$null = New-Item $HKLM_WinPSKeyRoot}
+ $WinKeyPath = Join-Path $HKLM_WinPSKeyRoot 'UpdatableHelp'
+ if (-not (Test-Path $WinKeyPath)) {$null = New-Item $WinKeyPath}
+
+ TestFeature -KeyPath $WinKeyPath
+
+ Remove-item $HKLM_KeyRoot -Recurse -Force
+ Remove-item $HKLM_WinPSKeyRoot -Recurse -Force
+ }
+
+ It 'Session configuration policy test' {
+ function TestFeature
+ {
+ param([string]$KeyPath)
+
+ # set policy to use unique non-existing configuration session name
+ $SessionName = "TestSessionConfiguration-$(get-random)"
+ Set-ItemProperty -Path $KeyPath -Name EnableConsoleSessionConfiguration -Value 1 -Force
+ Set-ItemProperty -Path $KeyPath -Name ConsoleSessionConfigurationName -Value $SessionName -Force
+
+ $LogPath = (New-TemporaryFile).FullName
+ pwsh -NoProfile -NonInteractive -c "1" *> $LogPath # this implicitly uses SessionConfiguration from the policy
+
+ # Log should have an error that has our configuration session name; e.g.:
+ # 'The shell cannot be started. A failure occurred during initialization:
+ # Cannot create or open the configuration session 116337267.'
+
+ $Log = Get-Content $LogPath -Raw
+ $Log.Contains("$SessionName") | should be $True
+ Remove-Item -Path $LogPath -Force
+ }
+
+ $KeyPath = Join-Path $KeyRoot 'ConsoleSessionConfiguration'
+ if (-not (Test-Path $KeyPath)) {$null = New-Item $KeyPath}
+
+ TestFeature -KeyPath $KeyPath
+ }
+ }
+}
diff --git a/tools/releaseBuild/signing.xml b/tools/releaseBuild/signing.xml
index 95837fbeb0..017dc425e0 100644
--- a/tools/releaseBuild/signing.xml
+++ b/tools/releaseBuild/signing.xml
@@ -30,6 +30,7 @@
+