Create the default PSSession configuration, not tied to a specific PowerShell version. (#6519)

Create the default PSSession configuration, not tied to a specific PowerShell version.

When Enable-PSRemoting command is run, it creates 2 sessions configurations:

first, the same as it was before with the name containing the current version expressed as: 'PowerShell.$PSVersionTable.GitCommitId'
second with the default name 'PowerShell.6' so that administrators wouldn't have to guess which specific version is installed on the target.
PR addresses the issue: #6470
This commit is contained in:
Klaudia Algiz
2018-04-10 12:21:44 -07:00
committed by Travis Plunk
parent aefc0d89b0
commit 3cd2308af9
3 changed files with 255 additions and 150 deletions
@@ -188,14 +188,13 @@ function Register-PSSessionConfiguration
}}
}}
if (([System.Management.Automation.Runspaces.PSSessionConfigurationAccessMode]::Local.Equals($accessMode) -or
([System.Management.Automation.Runspaces.PSSessionConfigurationAccessMode]::Remote.Equals($accessMode)-and $disableNetworkExists)) -and
!$haveDisableACE)
([System.Management.Automation.Runspaces.PSSessionConfigurationAccessMode]::Remote.Equals($accessMode))) -and !$haveDisableACE)
{{
# Add network deny ACE for local access or remote access with PSRemoting disabled ($disableNetworkExists)
# Add network deny ACE for local access or remote access with PSRemoting disabled.
$sd.DiscretionaryAcl.AddAccess(""deny"", $networkSID, 268435456, ""None"", ""None"")
$newSDDL = $sd.GetSddlForm(""all"")
}}
if ([System.Management.Automation.Runspaces.PSSessionConfigurationAccessMode]::Remote.Equals($accessMode) -and -not $disableNetworkExists -and $haveDisableACE)
if ([System.Management.Automation.Runspaces.PSSessionConfigurationAccessMode]::Remote.Equals($accessMode) -and $haveDisableACE)
{{
# Remove the specific ACE
$sd.discretionaryacl.RemoveAccessSpecific('Deny', $securityIdentifierToPurge, 268435456, 'none', 'none')
@@ -4796,16 +4795,119 @@ $_ | Disable-PSSessionConfiguration -force $args[0] -whatif:$args[1] -confirm:$a
//TODO: CLR4: Remove the logic for setting the MaxMemoryPerShellMB to 200 MB once IPMO->Get-Command->Get-Help memory usage issue is fixed.
private const string enableRemotingSbFormat = @"
function Generate-PluginConfigFile
Set-StrictMode -Version Latest
function New-PluginConfigFile
{{
[CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact=""Medium"")]
param(
[Parameter()] [string] $pluginInstallPath
)
$pluginConfigFile = Join-Path $pluginInstallPath ""RemotePowerShellConfig.txt""
# This always overwrites the file with a new version of it (if it already exists)
Set-Content -Path $pluginConfigFile -Value ""PSHOMEDIR=$PSHOME""
Add-Content -Path $pluginConfigFile -Value ""CORECLRDIR=$PSHOME""
Set-Content -Path $pluginConfigFile -Value ""PSHOMEDIR=$PSHOME"" -ErrorAction Stop
Add-Content -Path $pluginConfigFile -Value ""CORECLRDIR=$PSHOME"" -ErrorAction Stop
}}
function Copy-PluginToEndpoint
{{
param(
[Parameter()] [string] $endpointDir
)
$resolvedPluginInstallPath = """"
$pluginInstallPath = Join-Path ([System.Environment]::GetFolderPath([System.Environment+SpecialFolder]::Windows) + ""\System32\PowerShell"") $endpointDir
if (!(Test-Path $pluginInstallPath))
{{
$resolvedPluginInstallPath = New-Item -Type Directory -Path $pluginInstallPath
}}
else
{{
$resolvedPluginInstallPath = Resolve-Path $pluginInstallPath
}}
if (!(Test-Path $resolvedPluginInstallPath\{5}))
{{
Copy-Item -Path $PSHOME\{5} -Destination $resolvedPluginInstallPath -Force -ErrorAction Stop
if (!(Test-Path $resolvedPluginInstallPath\{5}))
{{
Write-Error ($errorMsgUnableToInstallPlugin -f ""{5}"", $resolvedPluginInstallPath)
return $null
}}
}}
return $resolvedPluginInstallPath
}}
function Register-Endpoint
{{
param(
[Parameter()] [string] $configurationName
)
#
# Section 1:
# Move pwrshplugin.dll from $PSHOME to the endpoint directory
#
# The plugin directory pattern for endpoint configuration is:
# '$env:WINDIR\System32\PowerShell\' + powershell_version,
# so we call Copy-PluginToEndpoint function only with the PowerShell version argument.
$pwshVersion = $configurationName.Replace(""PowerShell."", """")
$resolvedPluginInstallPath = Copy-PluginToEndpoint $pwshVersion
if (!$resolvedPluginInstallPath) {{
return
}}
#
# Section 2:
# Generate the Plugin Configuration File
#
New-PluginConfigFile $resolvedPluginInstallPath
#
# Section 3:
# Register the endpoint
#
$null = Register-PSSessionConfiguration -Name $configurationName -force -ErrorAction Stop
set-item -WarningAction SilentlyContinue wsman:\localhost\plugin\$configurationName\Quotas\MaxShellsPerUser -value ""25"" -confirm:$false
set-item -WarningAction SilentlyContinue wsman:\localhost\plugin\$configurationName\Quotas\MaxIdleTimeoutms -value {4} -confirm:$false
restart-service winrm -confirm:$false
}}
function Register-EndpointIfNotPresent
{{
[CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact=""Medium"")]
param(
[Parameter()] [string] $Name,
[Parameter()] [bool] $Force,
[Parameter()] [string] $queryForRegisterDefault,
[Parameter()] [string] $captionForRegisterDefault
)
#
# This cmdlet will make sure default powershell end points exist upon successful completion.
#
# Windows PowerShell:
# Microsoft.PowerShell
# Microsoft.PowerShell32 (wow64)
#
# PowerShell Core:
# PowerShell.<version ID>
#
$errorCount = $error.Count
$endPoint = Get-PSSessionConfiguration $Name -Force:$Force -ErrorAction silentlycontinue 2>&1
$newErrorCount = $error.Count
# remove the 'No Session Configuration matches criteria' errors
for ($index = 0; $index -lt ($newErrorCount - $errorCount); $index ++)
{{
$error.RemoveAt(0)
}}
$qMessage = $queryForRegisterDefault -f ""$Name"",""Register-PSSessionConfiguration {0} -force""
if ((!$endpoint) -and
($force -or $pscmdlet.ShouldProcess($qMessage, $captionForRegisterDefault)))
{{
Register-Endpoint $Name
}}
}}
function Enable-PSRemoting
@@ -4836,70 +4938,16 @@ param(
# first try to enable all the sessions
Enable-PSSessionConfiguration @PSBoundParameters
#
# This cmdlet will make sure default powershell end points exist upon successful completion.
#
# Windows PowerShell:
# Microsoft.PowerShell
# Microsoft.PowerShell32 (wow64)
#
# PowerShell Core:
# PowerShell.<version ID>
#
$errorCount = $error.Count
$endPoint = Get-PSSessionConfiguration {0} -Force:$Force -ErrorAction silentlycontinue 2>&1
$newErrorCount = $error.Count
Register-EndpointIfNotPresent -Name {0} $Force $queryForRegisterDefault $captionForRegisterDefault
# remove the 'No Session Configuration matches criteria' errors
for ($index = 0; $index -lt ($newErrorCount - $errorCount); $index ++)
{{
$error.RemoveAt(0)
}}
$qMessage = $queryForRegisterDefault -f ""{0}"",""Register-PSSessionConfiguration {0} -force""
if ((!$endpoint) -and
($force -or $pscmdlet.ShouldProcess($qMessage, $captionForRegisterDefault)))
{{
$resolvedPluginInstallPath = """"
#
# Section 1:
# Move pwrshplugin.dll from $PSHOME to the endpoint directory
#
$pluginInstallPath = Join-Path ""$env:WINDIR\System32\PowerShell"" $psversiontable.GitCommitId
if (!(Test-Path $pluginInstallPath))
{{
$resolvedPluginInstallPath = New-Item -Type Directory -Path $pluginInstallPath
}}
else
{{
$resolvedPluginInstallPath = Resolve-Path $pluginInstallPath
}}
if (!(Test-Path $resolvedPluginInstallPath\{5}))
{{
Copy-Item $PSHOME\{5} $resolvedPluginInstallPath -Force
if (!(Test-Path $resolvedPluginInstallPath\{5}))
{{
Write-Error ($errorMsgUnableToInstallPlugin -f ""{5}"", $resolvedPluginInstallPath)
return
}}
}}
#
# Section 2:
# Generate the Plugin Configuration File
#
Generate-PluginConfigFile $resolvedPluginInstallPath
#
# Section 3:
# Register the endpoint
#
$null = Register-PSSessionConfiguration -Name {0} -force
set-item -WarningAction SilentlyContinue wsman:\localhost\plugin\{0}\Quotas\MaxShellsPerUser -value ""25"" -confirm:$false
set-item -WarningAction SilentlyContinue wsman:\localhost\plugin\{0}\Quotas\MaxIdleTimeoutms -value {4} -confirm:$false
restart-service winrm -confirm:$false
# Create the default PSSession configuration, not tied to specific PowerShell version
# e. g. 'PowerShell.6'.
$powershellVersionMajor = $PSVersionTable.PSVersion.ToString()
$dotPos = $powershellVersionMajor.IndexOf(""."")
if ($dotPos -ne -1) {{
$powershellVersionMajor = $powershellVersionMajor.Substring(0, $dotPos)
}}
Register-EndpointIfNotPresent -Name (""PowerShell."" + $powershellVersionMajor) $Force $queryForRegisterDefault $captionForRegisterDefault
# PowerShell Workflow and WOW are not supported for PowerShell Core
if (![System.Management.Automation.Platform]::IsCoreCLR)
@@ -21,10 +21,18 @@
param
(
[parameter(Mandatory = $true, ParameterSetName = "ByPath")]
[switch]$Force,
[string]
$PowerShellHome
)
Set-StrictMode -Version Latest
if (! ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator"))
{
Write-Error "WinRM registration requires Administrator rights. To run this cmdlet, start PowerShell with the `"Run as administrator`" option."
return
}
function Register-WinRmPlugin
{
param
@@ -49,8 +57,6 @@ function Register-WinRmPlugin
$regKey = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WSMAN\Plugin\$pluginEndpointName"
$regKeyName = '"ConfigXML"="{0}"'
$pluginArchitecture = "64"
if ($env:PROCESSOR_ARCHITECTURE -match "x86" -or $env:PROCESSOR_ARCHITECTURE -eq "ARM")
{
@@ -80,8 +86,9 @@ function Register-WinRmPlugin
New-ItemProperty -Path $regKey -Name ConfigXML -Value $valueString > $null
}
function Generate-PluginConfigFile
function New-PluginConfigFile
{
[CmdletBinding(SupportsShouldProcess, ConfirmImpact="Medium")]
param
(
[string]
@@ -97,89 +104,118 @@ function Generate-PluginConfigFile
# This always overwrites the file with a new version of it if the
# script is invoked multiple times.
Set-Content -Path $pluginFile -Value "PSHOMEDIR=$targetPsHomeDir"
Add-Content -Path $pluginFile -Value "CORECLRDIR=$targetPsHomeDir"
Set-Content -Path $pluginFile -Value "PSHOMEDIR=$targetPsHomeDir" -ErrorAction Stop
Add-Content -Path $pluginFile -Value "CORECLRDIR=$targetPsHomeDir" -ErrorAction Stop
Write-Verbose "Created Plugin Config File: $pluginFile" -Verbose
}
######################
# #
# Install the plugin #
# #
######################
function Install-PluginEndpoint {
[CmdletBinding(SupportsShouldProcess, ConfirmImpact="Medium")]
param (
[Parameter()] [bool] $Force,
[switch]
$VersionIndependent
)
if (! ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator"))
{
Write-Error "WinRM registration requires Administrator rights. To run this cmdlet, start PowerShell with the `"Run as administrator`" option."
Break
######################
# #
# Install the plugin #
# #
######################
if ($PsCmdlet.ParameterSetName -eq "ByPath")
{
$targetPsHome = $PowerShellHome
$targetPsVersion = & "$targetPsHome\pwsh" -NoProfile -Command '$PSVersionTable.PSVersion.ToString()'
}
else
{
## Get the PSHome and PSVersion using the current powershell instance
$targetPsHome = $PSHOME
$targetPsVersion = $PSVersionTable.PSVersion.ToString()
}
# For default, not tied to the specific version endpoint, we apply
# only first number in the PSVersion string to the endpoint name.
# Example name: 'PowerShell.6'.
if ($VersionIndependent) {
$dotPos = $targetPsVersion.IndexOf(".")
if ($dotPos -ne -1) {
$targetPsVersion = $targetPsVersion.Substring(0, $dotPos)
}
}
Write-Verbose "Using PowerShell Version: $targetPsVersion" -Verbose
$pluginEndpointName = "PowerShell.$targetPsVersion"
$endPoint = Get-PSSessionConfiguration $pluginEndpointName -Force:$Force -ErrorAction silentlycontinue 2>&1
# If endpoint exists and -Force parameter was not used, the endpoint would not be overwritten.
if ($endpoint -and !$Force)
{
Write-Error -Category ResourceExists -ErrorId "PSSessionConfigurationExists" -Message "Endpoint $pluginEndpointName already exists."
return
}
if (!$PSCmdlet.ShouldProcess($pluginEndpointName)) {
return
}
$pluginBasePath = Join-Path ([System.Environment]::GetFolderPath([System.Environment+SpecialFolder]::Windows) + "\System32\PowerShell") $targetPsVersion
$resolvedPluginAbsolutePath = ""
if (! (Test-Path $pluginBasePath))
{
Write-Verbose "Creating $pluginBasePath"
$resolvedPluginAbsolutePath = New-Item -Type Directory -Path $pluginBasePath
}
else
{
$resolvedPluginAbsolutePath = Resolve-Path $pluginBasePath
}
$pluginPath = Join-Path $resolvedPluginAbsolutePath "pwrshplugin.dll"
# This is forced to ensure the the file is placed correctly
Copy-Item $targetPsHome\pwrshplugin.dll $resolvedPluginAbsolutePath -Force -Verbose -ErrorAction Stop
$pluginFile = Join-Path $resolvedPluginAbsolutePath "RemotePowerShellConfig.txt"
New-PluginConfigFile $pluginFile (Resolve-Path $targetPsHome)
# Register the plugin
Register-WinRmPlugin $pluginPath $pluginEndpointName
####################################################################
# #
# Validations to confirm that everything was registered correctly. #
# #
####################################################################
if (! (Test-Path $pluginFile))
{
throw "WinRM Plugin configuration file not created. Expected = $pluginFile"
}
if (! (Test-Path $resolvedPluginAbsolutePath\pwrshplugin.dll))
{
throw "WinRM Plugin DLL missing. Expected = $resolvedPluginAbsolutePath\pwrshplugin.dll"
}
try
{
Write-Host "`nGet-PSSessionConfiguration $pluginEndpointName" -foregroundcolor "green"
Get-PSSessionConfiguration $pluginEndpointName -ErrorAction Stop
}
catch [Microsoft.PowerShell.Commands.WriteErrorException]
{
throw "No remoting session configuration matches the name $pluginEndpointName."
}
}
if ($PsCmdlet.ParameterSetName -eq "ByPath")
{
$targetPsHome = $PowerShellHome
$targetPsVersion = & "$targetPsHome\pwsh" -NoProfile -Command '$PSVersionTable.PSVersion.ToString()'
}
else
{
## Get the PSHome and PSVersion using the current powershell instance
$targetPsHome = $PSHOME
$targetPsVersion = $PSVersionTable.PSVersion.ToString()
}
Write-Verbose "Using PowerShell Version: $targetPsVersion" -Verbose
$pluginBasePath = Join-Path "$env:WINDIR\System32\PowerShell" $targetPsVersion
$resolvedPluginAbsolutePath = ""
if (! (Test-Path $pluginBasePath))
{
Write-Verbose "Creating $pluginBasePath"
$resolvedPluginAbsolutePath = New-Item -Type Directory -Path $pluginBasePath
}
else
{
$resolvedPluginAbsolutePath = Resolve-Path $pluginBasePath
}
$pluginPath = Join-Path $resolvedPluginAbsolutePath "pwrshplugin.dll"
# This is forced to ensure the the file is placed correctly
Copy-Item $targetPsHome\pwrshplugin.dll $resolvedPluginAbsolutePath -Force -Verbose
$pluginFile = Join-Path $resolvedPluginAbsolutePath "RemotePowerShellConfig.txt"
Generate-PluginConfigFile $pluginFile (Resolve-Path $targetPsHome)
$pluginEndpointName = "powershell.$targetPsVersion"
# Register the plugin
Register-WinRmPlugin $pluginPath $pluginEndpointName
####################################################################
# #
# Validations to confirm that everything was registered correctly. #
# #
####################################################################
if (! (Test-Path $pluginFile))
{
throw "WinRM Plugin configuration file not created. Expected = $pluginFile"
}
if (! (Test-Path $resolvedPluginAbsolutePath\pwrshplugin.dll))
{
throw "WinRM Plugin DLL missing. Expected = $resolvedPluginAbsolutePath\pwrshplugin.dll"
}
try
{
Write-Host "`nGet-PSSessionConfiguration $pluginEndpointName" -foregroundcolor "green"
Get-PSSessionConfiguration $pluginEndpointName -ErrorAction Stop
}
catch [Microsoft.PowerShell.Commands.WriteErrorException]
{
throw "No remoting session configuration matches the name $pluginEndpointName."
}
Install-PluginEndpoint -Force $Force
Install-PluginEndpoint -Force $Force -VersionIndependent
Write-Host "Restarting WinRM to ensure that the plugin configuration change takes effect.`nThis is required for WinRM running on Windows SKUs prior to Windows 10." -foregroundcolor Magenta
Restart-Service winrm
@@ -129,7 +129,7 @@ try
$Result = Get-PSSessionConfiguration
$Result.Name -contains $endpointName | Should -BeTrue
$Result.PSVersion | Should -BeExactly $expectedPSVersion
$Result.PSVersion -contains $expectedPSVersion | Should -BeTrue
}
It "Get-PSSessionConfiguration with Name parameter" {
@@ -147,7 +147,7 @@ try
$Result = Get-PSSessionConfiguration -Name $endpointWildcard
$Result.Name -contains $endpointName | Should -BeTrue
$Result.PSVersion | Should -BeExactly $expectedPSVersion
$Result.PSVersion -contains $expectedPSVersion | Should -BeTrue
}
It "Get-PSSessionConfiguration -Name with Non-Existent session configuration" {
@@ -838,6 +838,27 @@ namespace PowershellTestConfigNamespace
$result | Should -BeTrue
}
}
Describe "Validate Enable-PSSession Cmdlet" -Tags @("Feature", 'RequireAdminOnWindows') {
BeforeAll {
if ($IsNotSkipped) {
Enable-PSRemoting
}
}
It "Enable-PSSession Cmdlet creates a PSSession configuration with a name tied to PowerShell version." {
$endpointName = "PowerShell." + $PSVersionTable.GitCommitId
$matchedEndpoint = Get-PSSessionConfiguration $endpointName -ErrorAction SilentlyContinue
$matchedEndpoint | Should -Not -BeNullOrEmpty
}
It "Enable-PSSession Cmdlet creates a default PSSession configuration untied to a specific PowerShell version." {
$dotPos = $PSVersionTable.PSVersion.ToString().IndexOf(".")
$endpointName = "PowerShell." + $PSVersionTable.PSVersion.ToString().Substring(0, $dotPos)
$matchedEndpoint = Get-PSSessionConfiguration $endpointName -ErrorAction SilentlyContinue
$matchedEndpoint | Should -Not -BeNullOrEmpty
}
}
}
finally
{