From ed29ad1506925901625a29f412680f97ca8010d6 Mon Sep 17 00:00:00 2001 From: Andrew Date: Thu, 12 Sep 2019 13:41:59 -0700 Subject: [PATCH] Adding PSCore group policy definitions (#10468) --- .../InstallPSCorePolicyDefinitions.ps1 | 88 +++++++ .../PowerShellCoreExecutionPolicy.adml | 125 +++++++++ .../PowerShellCoreExecutionPolicy.admx | 119 +++++++++ assets/files.wxs | 12 + .../engine/Utils.cs | 98 +++++-- .../powershell-win-core.csproj | 2 +- .../Basic/GroupPolicySettings.Tests.ps1 | 248 ++++++++++++++++++ tools/releaseBuild/signing.xml | 1 + 8 files changed, 666 insertions(+), 27 deletions(-) create mode 100644 assets/GroupPolicy/InstallPSCorePolicyDefinitions.ps1 create mode 100644 assets/GroupPolicy/PowerShellCoreExecutionPolicy.adml create mode 100644 assets/GroupPolicy/PowerShellCoreExecutionPolicy.admx create mode 100644 test/powershell/engine/Basic/GroupPolicySettings.Tests.ps1 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 @@ PreserveNewest PreserveNewest - + PreserveNewest PreserveNewest 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 @@ +