diff --git a/build.psm1 b/build.psm1 index 8e31992d398..1e06405c285 100644 --- a/build.psm1 +++ b/build.psm1 @@ -252,7 +252,8 @@ cmd.exe /C cd /d "$location" "&" "$($vcPath)\vcvarsall.bat" "$Arch" "&" cmake "$ $FilesToCopy = @('pwrshplugin.dll', 'pwrshplugin.pdb') $dstPath = "$PSScriptRoot\src\powershell-win-core" $FilesToCopy | ForEach-Object { - $srcPath = Join-Path (Join-Path (Join-Path (Get-Location) "bin") $Configuration) "CoreClr/$_" + $srcPath = [IO.Path]::Combine((Get-Location), "bin", $Configuration, "CoreClr/$_") + log " Copying $srcPath to $dstPath" Copy-Item $srcPath $dstPath } diff --git a/src/System.Management.Automation/engine/InitialSessionState.cs b/src/System.Management.Automation/engine/InitialSessionState.cs index 235b1dc943a..9f6783793ff 100644 --- a/src/System.Management.Automation/engine/InitialSessionState.cs +++ b/src/System.Management.Automation/engine/InitialSessionState.cs @@ -5963,6 +5963,10 @@ private static void InitializeCoreCmdletsAndProviders( {"Debug-Job", new SessionStateCmdletEntry("Debug-Job", typeof(DebugJobCommand), helpFile) }, {"Disable-PSSessionConfiguration", new SessionStateCmdletEntry("Disable-PSSessionConfiguration", typeof(DisablePSSessionConfigurationCommand), helpFile) }, {"Disconnect-PSSession", new SessionStateCmdletEntry("Disconnect-PSSession", typeof(DisconnectPSSessionCommand), helpFile) }, +#if !UNIX + {"Disable-PSRemoting", new SessionStateCmdletEntry("Disable-PSRemoting", typeof(DisablePSRemotingCommand), helpFile) }, + {"Enable-PSRemoting", new SessionStateCmdletEntry("Enable-PSRemoting", typeof(EnablePSRemotingCommand), helpFile) }, +#endif {"Enable-PSSessionConfiguration", new SessionStateCmdletEntry("Enable-PSSessionConfiguration", typeof(EnablePSSessionConfigurationCommand), helpFile) }, {"Enter-PSHostProcess", new SessionStateCmdletEntry("Enter-PSHostProcess", typeof(EnterPSHostProcessCommand), helpFile) }, {"Enter-PSSession", new SessionStateCmdletEntry("Enter-PSSession", typeof(EnterPSSessionCommand), helpFile) }, @@ -6013,8 +6017,6 @@ private static void InitializeCoreCmdletsAndProviders( {"Where-Object", new SessionStateCmdletEntry("Where-Object", typeof(WhereObjectCommand), helpFile) }, #if !CORECLR {"Add-PSSnapin", new SessionStateCmdletEntry("Add-PSSnapin", typeof(AddPSSnapinCommand), helpFile) }, - {"Disable-PSRemoting", new SessionStateCmdletEntry("Disable-PSRemoting", typeof(DisablePSRemotingCommand), helpFile) }, - {"Enable-PSRemoting", new SessionStateCmdletEntry("Enable-PSRemoting", typeof(EnablePSRemotingCommand), helpFile) }, {"Export-Console", new SessionStateCmdletEntry("Export-Console", typeof(ExportConsoleCommand), helpFile) }, {"Get-PSSnapin", new SessionStateCmdletEntry("Get-PSSnapin", typeof(GetPSSnapinCommand), helpFile) }, {"Remove-PSSnapin", new SessionStateCmdletEntry("Remove-PSSnapin", typeof(RemovePSSnapinCommand), helpFile) }, diff --git a/src/System.Management.Automation/engine/remoting/commands/CustomShellCommands.cs b/src/System.Management.Automation/engine/remoting/commands/CustomShellCommands.cs index 0ef40bd39a1..c28b7939ddf 100644 --- a/src/System.Management.Automation/engine/remoting/commands/CustomShellCommands.cs +++ b/src/System.Management.Automation/engine/remoting/commands/CustomShellCommands.cs @@ -107,7 +107,7 @@ function Register-PSSessionConfiguration {{ if ($force) {{ - if (Test-Path WSMan:\localhost\Plugin\""$pluginName"") + if (Test-Path (Join-Path WSMan:\localhost\Plugin ""$pluginName"")) {{ Unregister-PSSessionConfiguration -name ""$pluginName"" -force }} @@ -129,9 +129,11 @@ function Register-PSSessionConfiguration {{ try {{ $runAsCredential = new-object system.management.automation.PSCredential($runAsUserName, $runAsPassword) - set-item -WarningAction SilentlyContinue WSMan:\localhost\Plugin\""$pluginName""\RunAsUser $runAsCredential -confirm:$false + $pluginWsmanRunAsUserPath = [System.IO.Path]::Combine(""WSMan:\localhost\Plugin"", ""$pluginName"", ""RunAsUser"") + set-item -WarningAction SilentlyContinue $pluginWsmanRunAsUserPath $runAsCredential -confirm:$false }} catch {{ - remove-item WSMan:\localhost\Plugin\""$pluginName"" -recurse -force + + remove-item (Join-Path WSMan:\localhost\Plugin ""$pluginName"") -recurse -force write-error $_ # Do not add anymore clean up code after Write-Error, because if EA=Stop is set by user # any code at this point will not execute. @@ -238,7 +240,7 @@ function Register-PSSessionConfiguration }} }} catch {{ - remove-item WSMan:\localhost\Plugin\""$pluginName"" -recurse -force + remove-item (Join-Path WSMan:\localhost\Plugin ""$pluginName"") -recurse -force write-error $_ # Do not add anymore clean up code after Write-Error, because if EA=Stop is set by user # any code at this point will not execute. @@ -279,7 +281,7 @@ function Register-PSSessionConfiguration private const string pluginXmlFormat = @" @@ -485,6 +487,15 @@ protected override void BeginProcessing() ThrowTerminatingError(ioe.ErrorRecord); } } + + string pluginPath = PSSessionConfigurationCommandUtilities.GetWinrmPluginDllPath(); + pluginPath = Environment.ExpandEnvironmentVariables(pluginPath); + if (!System.IO.File.Exists(pluginPath)) + { + PSInvalidOperationException ioe = new PSInvalidOperationException( + StringUtil.Format(RemotingErrorIdStrings.PluginDllMissing, RemotingConstants.PSPluginDLLName)); + ThrowTerminatingError(ioe.ErrorRecord); + } } /// @@ -764,10 +775,11 @@ private string ConstructTemporaryFile(string pluginContent) try { - StreamWriter fileStream = File.CreateText(tmpFileName); - fileStream.Write(pluginContent); - fileStream.Flush(); - fileStream.Dispose(); + using (StreamWriter fileStream = File.CreateText(tmpFileName)) + { + fileStream.Write(pluginContent); + fileStream.Flush(); + } } catch (UnauthorizedAccessException uae) { @@ -988,8 +1000,14 @@ private string ConstructPluginContent(out string srcConfigFilePath, out string d if (string.Equals(procArch, "amd64", StringComparison.OrdinalIgnoreCase) || string.Equals(procArch, "ia64", StringComparison.OrdinalIgnoreCase)) { +#if CORECLR + InvalidOperationException ioe = new InvalidOperationException(RemotingErrorIdStrings.InvalidProcessorArchitecture); + ErrorRecord er = new ErrorRecord(ioe, "InvalidProcessorArchitecture", ErrorCategory.InvalidArgument, Path); + ThrowTerminatingError(er); +#else // syswow64 is applicable only on 64 bit platforms. destPath = destPath.ToLowerInvariant().Replace("\\system32\\", "\\syswow64\\"); +#endif } } @@ -1000,6 +1018,12 @@ private string ConstructPluginContent(out string srcConfigFilePath, out string d destConfigFilePath = destPath; // Copy File. + string destConfigFileDirectory = System.IO.Path.GetDirectoryName(destConfigFilePath); + + // The directory is not auto-created for PowerShell Core. + // The call will create it or return its path if it already exists + System.IO.Directory.CreateDirectory(destConfigFileDirectory); + File.Copy(srcConfigFilePath, destConfigFilePath, true); initParameters.Append(string.Format(CultureInfo.InvariantCulture, @@ -1241,10 +1265,12 @@ private string ConstructPluginContent(out string srcConfigFilePath, out string d (transportOption as WSManConfigurationOption).ProcessIdleTimeoutSec = 0; } + string psPluginDllPath = PSSessionConfigurationCommandUtilities.GetWinrmPluginDllPath(); + string result = string.Format(CultureInfo.InvariantCulture, pluginXmlFormat, shellName, /* {0} */ - RemotingConstants.PSPluginDLLName, /* {1} */ + psPluginDllPath, /* {1} */ architectureParameter, /* {2} */ initParameters.ToString(), /* {3} */ WSManNativeApi.ResourceURIPrefix + shellName, /* {4} */ @@ -1528,6 +1554,33 @@ internal static string GetRunAsVirtualAccountGroupsString(string[] groups) return string.Join(";", groups); } + /// + /// Returns the default WinRM plugin shell name for this instance of PowerShell + /// + /// + internal static string GetWinrmPluginShellName() + { + // PowerShell Core uses a versioned directory to hold the plugin + Hashtable versionTable = PSVersionInfo.GetPSVersionTable(); + // TODO: This should be PSVersionInfo.PSVersionName once we get + // closer to release. Right now it doesn't support alpha versions. + return System.String.Concat("PowerShell.", (string)versionTable["GitCommitId"]); + } + + /// + /// Returns the default WinRM plugin DLL file path for this instance of PowerShell + /// + /// + internal static string GetWinrmPluginDllPath() + { + // PowerShell Core uses its versioned directory instead of system32 + Hashtable versionTable = PSVersionInfo.GetPSVersionTable(); + // TODO: This should be PSVersionInfo.PSVersionName once we get + // closer to release. Right now it doesn't support alpha versions. + string pluginDllDirectory = System.IO.Path.Combine("%windir%\\system32\\PowerShell", (string)versionTable["GitCommitId"]); + return System.IO.Path.Combine(pluginDllDirectory, RemotingConstants.PSPluginDLLName); + } + #endregion #region Group Conditional SDDL @@ -2493,7 +2546,16 @@ function Unregister-PSSessionConfiguration {{ return }} - + else + {{ + if (($pluginFileName.Value -match 'system32\\{0}') -OR + ($pluginFileName.Value -match 'syswow64\\{0}')) + {{ + # Filter out WindowsPowerShell endpoints when running as PowerShell Core + return + }} + }} + $shellsFound++ $shouldProcessTargetString = $targetTemplate -f $_.Name @@ -2717,9 +2779,12 @@ function ExtractPluginProperties([string]$pluginDir, $objectToWriteTo) }} Get-Details $pluginDir $h - - if ($h[""AssemblyName""] -eq ""Microsoft.PowerShell.Workflow.ServiceCore, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"") {{ - + + # Workflow is not supported in PowerShell Core. Attempting to load the + # assembly results in a FileNotFoundException. + if (![System.Management.Automation.Platform]::IsCoreCLR -AND + $h[""AssemblyName""] -eq ""Microsoft.PowerShell.Workflow.ServiceCore, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"") {{ + $serviceCore = [Reflection.Assembly]::Load(""Microsoft.Powershell.Workflow.ServiceCore, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"") if ($null -ne $serviceCore) {{ @@ -2783,18 +2848,26 @@ function ExtractPluginProperties([string]$pluginDir, $objectToWriteTo) $shellNotErrMsgFormat = $args[1] $force = $args[2] $args[0] | ForEach-Object {{ - $shellsFound = 0; - $filter = $_ - Get-ChildItem 'WSMan:\localhost\Plugin\' -Force:$force | Where-Object {{ $_.name -like ""$filter"" }} | ForEach-Object {{ - $customPluginObject = new-object object - $customPluginObject.pstypenames.Insert(0, '{0}') - ExtractPluginProperties ""$($_.PSPath)"" $customPluginObject - # this is powershell based custom shell only if its plugin dll is pwrshplugin.dll - if (($customPluginObject.FileName) -and ($customPluginObject.FileName -match '{1}')) - {{ - $shellsFound++ - $customPluginObject - }} + $shellsFound = 0; + $filter = $_ + Get-ChildItem 'WSMan:\localhost\Plugin\' -Force:$force | ? {{ $_.name -like ""$filter"" }} | ForEach-Object {{ + $customPluginObject = new-object object + $customPluginObject.pstypenames.Insert(0, '{0}') + ExtractPluginProperties ""$($_.PSPath)"" $customPluginObject + # This is powershell based custom shell only if its plugin dll is pwrshplugin.dll + if (($customPluginObject.FileName) -and ($customPluginObject.FileName -match '{1}')) + {{ + # Filter the endpoints based on the typeof PowerShell that is + # executing the cmdlet. {1} in another location indicates that it + # is a PowerShell Core endpoint + if (!($customPluginObject.FileName -match 'system32\\{1}') -AND # WindowsPowerShell + !($customPluginObject.FileName -match 'syswow64\\{1}')) # WOW64 WindowsPowerShell + {{ + # Add the PowerShell Core endpoint when running as PowerShell Core + $shellsFound++ + $customPluginObject + }} + }} }} # end of foreach if (!$shellsFound -and !([System.Management.Automation.WildcardPattern]::ContainsWildcardCharacters($_))) @@ -3019,6 +3092,8 @@ function Set-RunAsCredential{{ [string]$resourceUri, [string]$pluginNotFoundErrorMsg, [string]$pluginNotPowerShellMsg, + [string]$pluginForPowerShellCoreMsg, + [string]$pluginForWindowsPowerShellMsg, [System.Management.Automation.Runspaces.PSSessionConfigurationAccessMode]$accessMode ) {{ @@ -3045,6 +3120,16 @@ function Set-RunAsCredential{{ Write-Error $pluginNotPowerShellMsg return }} + else + {{ + # Filter out WindowsPowerShell endpoints when running as PowerShell Core + if (($pluginFileName.Value -match 'system32\\{0}') -OR + ($pluginFileName.Value -match 'syswow64\\{0}')) + {{ + Write-Error $pluginForWindowsPowerShellMsg + return + }} + }} # set Initialization Parameters $initParametersPath = Join-Path ""$pluginDir"" 'InitializationParameters' @@ -3100,7 +3185,7 @@ function Set-RunAsCredential{{ $null = winrm configsddl $resourceUri }} - # If accessmode is 'Disabled', we don't bother to check the sddl + # If accessmode is Disabled, we do not bother to check the sddl if ([System.Management.Automation.Runspaces.PSSessionConfigurationAccessMode]::Disabled.Equals($accessMode)) {{ return @@ -3181,7 +3266,7 @@ function Set-RunAsCredential{{ }} }} -Set-PSSessionConfiguration $args[0] $args[1] $args[2] $args[3] $args[4] $args[5] $args[6] $args[7] $args[8] $args[9] +Set-PSSessionConfiguration $args[0] $args[1] $args[2] $args[3] $args[4] $args[5] $args[6] $args[7] $args[8] $args[9] $args[10] $args[11] "; private const string initParamFormat = @""; private const string privateDataFormat = @"{0}"; @@ -3439,6 +3524,8 @@ protected override void ProcessRecord() string shellNotFoundErrorMsg = StringUtil.Format(RemotingErrorIdStrings.CSCmdsShellNotFound, shellName); string shellNotPowerShellMsg = StringUtil.Format(RemotingErrorIdStrings.CSCmdsShellNotPowerShellBased, shellName); + string shellForPowerShellCoreMsg = StringUtil.Format(RemotingErrorIdStrings.CSCmdsPowerShellCoreShellNotModifiable, shellName); + string shellForWindowsPowerShellMsg = StringUtil.Format(RemotingErrorIdStrings.CSCmdsWindowsPowerShellCoreNotModifiable, shellName); // construct object to update the properties PSObject propertiesToUpdate = ConstructPropertiesForUpdate(); @@ -3463,6 +3550,8 @@ protected override void ProcessRecord() WSManNativeApi.ResourceURIPrefix + shellName, shellNotFoundErrorMsg, shellNotPowerShellMsg, + shellForPowerShellCoreMsg, + shellForWindowsPowerShellMsg, accessModeSpecified ? AccessMode : PSSessionConfigurationAccessMode.Disabled, }); @@ -4406,7 +4495,7 @@ protected override void EndProcessing() // if user did not specify any shell, act on the default shell. if (_shellsToEnable.Count == 0) { - _shellsToEnable.Add(RemotingConstants.DefaultShellName); + _shellsToEnable.Add(PSSessionConfigurationCommandUtilities.GetWinrmPluginShellName()); } WriteVerbose(StringUtil.Format(RemotingErrorIdStrings.EcsScriptMessageV, enablePluginSbFormat)); @@ -4645,7 +4734,7 @@ protected override void EndProcessing() // if user did not specify any shell, act on the default shell. if (_shellsToDisable.Count == 0) { - _shellsToDisable.Add(RemotingConstants.DefaultShellName); + _shellsToDisable.Add(PSSessionConfigurationCommandUtilities.GetWinrmPluginShellName()); } //WriteWarning(StringUtil.Format(RemotingErrorIdStrings.DcsWarningMessage)); @@ -4704,10 +4793,8 @@ protected override void EndProcessing() /// /// /// -#if !CORECLR [Cmdlet(VerbsLifecycle.Enable, RemotingConstants.PSRemotingNoun, SupportsShouldProcess = true, ConfirmImpact = ConfirmImpact.Medium, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=144300")] -#endif public sealed class EnablePSRemotingCommand : PSCmdlet { #region Private Data @@ -4718,6 +4805,18 @@ public sealed class EnablePSRemotingCommand : PSCmdlet //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 +{{ +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"" +}} + function Enable-PSRemoting {{ [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact=""Medium"")] @@ -4727,7 +4826,8 @@ function Enable-PSRemoting [Parameter()] [string] $captionForRegisterDefault, [Parameter()] [string] $queryForSet, [Parameter()] [string] $captionForSet, - [Parameter()] [bool] $skipNetworkProfileCheck + [Parameter()] [bool] $skipNetworkProfileCheck, + [Parameter()] [string] $errorMsgUnableToInstallPlugin ) end @@ -4738,16 +4838,23 @@ function Enable-PSRemoting $null = $PSBoundParameters.Remove(""captionForRegisterDefault"") $null = $PSBoundParameters.Remove(""queryForSet"") $null = $PSBoundParameters.Remove(""captionForSet"") + $null = $PSBoundParameters.Remove(""errorMsgUnableToInstallPlugin"") $PSBoundParameters.Add(""Name"",""*"") # first try to enable all the sessions Enable-PSSessionConfiguration @PSBoundParameters - # make sure default powershell end points exist - # ie., Microsoft.PowerShell - # and Microsoft.PowerShell32 (wow64) - + # + # This cmdlet will make sure default powershell end points exist upon successful completion. + # + # Windows PowerShell: + # Microsoft.PowerShell + # Microsoft.PowerShell32 (wow64) + # + # PowerShell Core: + # PowerShell. + # $errorCount = $error.Count $endPoint = Get-PSSessionConfiguration {0} -Force:$Force -ErrorAction silentlycontinue 2>&1 $newErrorCount = $error.Count @@ -4762,47 +4869,53 @@ function Enable-PSRemoting if ((!$endpoint) -and ($force -or $pscmdlet.ShouldProcess($qMessage, $captionForRegisterDefault))) {{ - $null = Register-PSSessionConfiguration {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 - }} + $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 + }} + }} - # Check Microsoft.PowerShell.Workflow endpoint - $errorCount = $error.Count - $endPoint = Get-PSSessionConfiguration {0}.workflow -Force:$Force -ErrorAction silentlycontinue 2>&1 - $newErrorCount = $error.Count + # + # Section 2: + # Generate the Plugin Configuration File + # + Generate-PluginConfigFile $resolvedPluginInstallPath - # remove the 'No Session Configuration matches criteria' errors - for ($index = 0; $index -lt ($newErrorCount - $errorCount); $index ++) - {{ - $error.RemoveAt(0) - }} + # + # Section 3: + # Register the endpoint + # + $null = Register-PSSessionConfiguration -Name {0} -force - if (!$endpoint) - {{ - $qMessage = $queryForRegisterDefault -f ""Microsoft.PowerShell.Workflow"",""Register-PSSessionConfiguration Microsoft.PowerShell.Workflow -force"" - if ($force -or $pscmdlet.ShouldProcess($qMessage, $captionForRegisterDefault)) {{ - $tempxmlfile = [io.path]::Gettempfilename() - ""{1}"" | out-file -force -filepath $tempxmlfile -confirm:$false - $null = winrm create winrm/config/plugin?Name=Microsoft.PowerShell.Workflow -file:$tempxmlfile - remove-item -path $tempxmlfile -force -confirm:$false - restart-service winrm -confirm:$false - }} + 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 }} - $pa = $env:PROCESSOR_ARCHITECTURE - if ($pa -eq ""x86"") + # PowerShell Workflow and WOW are not supported for PowerShell Core + if (![System.Management.Automation.Platform]::IsCoreCLR) {{ - # on 64-bit platforms, wow64 bit process has the correct architecture - # available in processor_architew6432 variable - $pa = $env:PROCESSOR_ARCHITEW6432 - }} - if ((($pa -eq ""amd64"")) -and (test-path $env:windir\syswow64\pwrshplugin.dll)) - {{ - # Check availability of WOW64 endpoint. Register if not available. + # Check Microsoft.PowerShell.Workflow endpoint $errorCount = $error.Count - $endPoint = Get-PSSessionConfiguration {0}32 -Force:$Force -ErrorAction silentlycontinue 2>&1 + $endPoint = Get-PSSessionConfiguration {0}.workflow -Force:$Force -ErrorAction silentlycontinue 2>&1 $newErrorCount = $error.Count # remove the 'No Session Configuration matches criteria' errors @@ -4811,17 +4924,50 @@ function Enable-PSRemoting $error.RemoveAt(0) }} - $qMessage = $queryForRegisterDefault -f ""{0}32"",""Register-PSSessionConfiguration {0}32 -processorarchitecture x86 -force"" - if ((!$endpoint) -and - ($force -or $pscmdlet.ShouldProcess($qMessage, $captionForRegisterDefault))) + if (!$endpoint) {{ - $null = Register-PSSessionConfiguration {0}32 -processorarchitecture x86 -force - set-item -WarningAction SilentlyContinue wsman:\localhost\plugin\{0}32\Quotas\MaxShellsPerUser -value ""25"" -confirm:$false - set-item -WarningAction SilentlyContinue wsman:\localhost\plugin\{0}32\Quotas\MaxIdleTimeoutms -value {4} -confirm:$false - restart-service winrm -confirm:$false + $qMessage = $queryForRegisterDefault -f ""Microsoft.PowerShell.Workflow"",""Register-PSSessionConfiguration Microsoft.PowerShell.Workflow -force"" + if ($force -or $pscmdlet.ShouldProcess($qMessage, $captionForRegisterDefault)) {{ + $tempxmlfile = [io.path]::Gettempfilename() + ""{1}"" | out-file -force -filepath $tempxmlfile -confirm:$false + $null = winrm create winrm/config/plugin?Name=Microsoft.PowerShell.Workflow -file:$tempxmlfile + remove-item -path $tempxmlfile -force -confirm:$false + restart-service winrm -confirm:$false + }} }} - }} + $pa = $env:PROCESSOR_ARCHITECTURE + if ($pa -eq ""x86"") + {{ + # on 64-bit platforms, wow64 bit process has the correct architecture + # available in processor_architew6432 variable + $pa = $env:PROCESSOR_ARCHITEW6432 + }} + if ((($pa -eq ""amd64"")) -and (test-path $env:windir\syswow64\pwrshplugin.dll)) + {{ + # Check availability of WOW64 endpoint. Register if not available. + $errorCount = $error.Count + $endPoint = Get-PSSessionConfiguration {0}32 -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 ""{0}32"",""Register-PSSessionConfiguration {0}32 -processorarchitecture x86 -force"" + if ((!$endpoint) -and + ($force -or $pscmdlet.ShouldProcess($qMessage, $captionForRegisterDefault))) + {{ + $null = Register-PSSessionConfiguration {0}32 -processorarchitecture x86 -force + set-item -WarningAction SilentlyContinue wsman:\localhost\plugin\{0}32\Quotas\MaxShellsPerUser -value ""25"" -confirm:$false + set-item -WarningAction SilentlyContinue wsman:\localhost\plugin\{0}32\Quotas\MaxIdleTimeoutms -value {4} -confirm:$false + restart-service winrm -confirm:$false + }} + }} + }} + # remove the 'network deny all' tag Get-PSSessionConfiguration -Force:$Force | ForEach-Object {{ $sddl = $null @@ -4888,7 +5034,7 @@ function Enable-PSRemoting }} # end of end block }} # end of Enable-PSRemoting -Enable-PSRemoting -force $args[0] -queryForRegisterDefault $args[1] -captionForRegisterDefault $args[2] -queryForSet $args[3] -captionForSet $args[4] -whatif:$args[5] -confirm:$args[6] -skipNetworkProfileCheck $args[7] +Enable-PSRemoting -force $args[0] -queryForRegisterDefault $args[1] -captionForRegisterDefault $args[2] -queryForSet $args[3] -captionForSet $args[4] -whatif:$args[5] -confirm:$args[6] -skipNetworkProfileCheck $args[7] -errorMsgUnableToInstallPlugin $args[8] "; private const string _workflowConfigXml = @" @@ -4942,11 +5088,11 @@ static EnablePSRemotingCommand() PSSessionConfigurationCommandBase.GetLocalSddl()); string enableRemotingScript = string.Format(CultureInfo.InvariantCulture, - enableRemotingSbFormat, RemotingConstants.DefaultShellName, + enableRemotingSbFormat, PSSessionConfigurationCommandUtilities.GetWinrmPluginShellName(), // Workflow endpoint configuration will be done through Register-PSSessionConfiguration // when the new features are available. workflowConfigXml, PSSessionConfigurationCommandBase.RemoteManagementUsersSID, PSSessionConfigurationCommandBase.InteractiveUsersSID, - RemotingConstants.MaxIdleTimeoutMS); + RemotingConstants.MaxIdleTimeoutMS, RemotingConstants.PSPluginDLLName); // compile the script block statically and reuse the same instance // everytime the command is run..This will save on parsing time. @@ -5039,7 +5185,8 @@ protected override void EndProcessing() setCaptionMessage, whatIf, confirm, - _skipNetworkProfileCheck}); + _skipNetworkProfileCheck, + RemotingErrorIdStrings.UnableToInstallPlugin}); } #endregion @@ -5054,10 +5201,8 @@ protected override void EndProcessing() /// Only disable the network access to the Session Configuration. The /// local access is still enabled /// -#if !CORECLR [Cmdlet(VerbsLifecycle.Disable, RemotingConstants.PSRemotingNoun, SupportsShouldProcess = true, ConfirmImpact = ConfirmImpact.Medium, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=144298")] -#endif public sealed class DisablePSRemotingCommand : PSCmdlet { # region Private Data diff --git a/src/System.Management.Automation/resources/RemotingErrorIdStrings.resx b/src/System.Management.Automation/resources/RemotingErrorIdStrings.resx index 5f9f5a98ed5..30290babfd6 100644 --- a/src/System.Management.Automation/resources/RemotingErrorIdStrings.resx +++ b/src/System.Management.Automation/resources/RemotingErrorIdStrings.resx @@ -655,6 +655,12 @@ Session configuration "{0}" is not a Windows PowerShell-based shell. + + + Session configuration "{0}" is a PowerShell Core-based shell. Please use PowerShell Core to modify it. + + + Session configuration "{0}" is a Windows PowerShell-based shell. Please use Windows PowerShell to modify it. No session configuration matches criteria "{0}". @@ -1639,6 +1645,15 @@ All WinRM sessions connected to Windows PowerShell session configurations, such The SSH transport process has abruptly terminated causing this remote session to break. + + PowerShell Core does not support WOW64. The binary must match the architecture of the processor. + + + Unable to install plugin {0} to directory {1}. + + + The WinRM plugin DLL {0} is missing for PowerShell Core. Please run Enable-PSRemoting and then retry this command. + This parameter set requires WSMan, and no supported WSMan client library was found. WSMan is either not installed or unavailable for this system. diff --git a/test/powershell/Modules/Microsoft.PowerShell.Core/PSSessionConfiguration.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Core/PSSessionConfiguration.Tests.ps1 new file mode 100644 index 00000000000..8df5594ab7d --- /dev/null +++ b/test/powershell/Modules/Microsoft.PowerShell.Core/PSSessionConfiguration.Tests.ps1 @@ -0,0 +1,849 @@ +try +{ + # Skip all tests on non-windows and non-PowerShellCore and non-elevated platforms. + $originalDefaultParameterValues = $PSDefaultParameterValues.Clone() + $IsNotSkipped = ($IsWindows -and $IsCoreCLR -and (Test-IsElevated)) + $PSDefaultParameterValues["it:skip"] = !$IsNotSkipped + + # + # TODO: Enable-PSRemoting should be performed at a higher set up for all tests. + # Tests whether PowerShell remoting is enabled for this instance of PowerShell. + # If remoting is not enabled, it will enable it and then clean up after all the tests + # have executed. + # + if ($IsNotSkipped) + { + $endpointName = "PowerShell.$($psversiontable.GitCommitId)" + + $matchedEndpoint = Get-PSSessionConfiguration $endpointName -ErrorAction SilentlyContinue + + if ($matchedEndpoint -eq $null) + { + # An endpoint for this instance of PowerShell does not exist. + # + # -SkipNetworkProfileCheck is used in case Docker or another application + # has created a publich virtual network profile on the system + Enable-PSRemoting -SkipNetworkProfileCheck + $endpointCreated = $true + } + } + + try + { + Describe "Validate Register-PSSessionConfiguration" -Tags @("CI", 'RequireAdminOnWindows') { + + AfterAll { + if ($IsNotSkipped) + { + Get-PSSessionConfiguration -Name "ITTask*" -ErrorAction SilentlyContinue | Unregister-PSSessionConfiguration + } + } + + It "Register-PSSessionConfiguration -TransportOption" { + + $ConfigurationName = "ITTask" + (Get-Random -Minimum 10000 -Maximum 99999) + $Transport = New-PSTransportOption -MaxSessions 40 -IdleTimeoutSec 3600 + + $null = Register-PSSessionConfiguration -Name $ConfigurationName -TransportOption $Transport + $result = Get-PSSessionConfiguration -Name $ConfigurationName + + $result.MaxShells | Should Be 40 + $result.IdleTimeoutms | Should Be 3600000 + } + } + Describe "Validate Get-PSSessionConfiguration, Enable-PSSessionConfiguration, Disable-PSSessionConfiguration, Unregister-PSSessionConfiguration cmdlets" -Tags @("CI", 'RequireAdminOnWindows') { + + BeforeAll { + if ($IsNotSkipped) + { + # Register new session configuration + function RegisterNewConfiguration { + param ( + + [string] + $Name, + + [string] + $ConfigFilePath, + + [switch] + $Enabled + ) + + $TestConfig = Get-PSSessionConfiguration -Name $Name -ErrorAction SilentlyContinue + if($TestConfig) + { + $null = Unregister-PSSessionConfiguration -Name $Name + } + + if($Enabled) + { + $null = Register-PSSessionConfiguration -Name $Name -Path $ConfigFilePath + } + else + { + $null = Register-PSSessionConfiguration -Name $Name -Path $ConfigFilePath -AccessMode Disabled + } + } + + # Unregister session configuration + function UnregisterPSSessionConfiguration{ + param ( + + [string] + $Name + ) + + Unregister-PSSessionConfiguration -Name $Name -Force -NoServiceRestart -ErrorAction SilentlyContinue + } + + # Create new Config File + function CreateTestConfigFile { + + $TestConfigFileLoc = join-path $TestDrive "Remoting" + if(-not (Test-path $TestConfigFileLoc)) + { + $null = New-Item -Path $TestConfigFileLoc -ItemType Directory -Force -ErrorAction Stop + } + + $TestConfigFile = join-path $TestConfigFileLoc "TestConfigFile.pssc" + $null = New-PSSessionConfigurationFile -Path $TestConfigFile -SessionType Default + + return $TestConfigFile + } + + $LocalConfigFilePath = CreateTestConfigFile + } + } + + Context "Validate Get-PSSessionConfiguration cmdlet" { + + It "Get-PSSessionConfiguration with no parameter" { + + $Result = Get-PSSessionConfiguration + + $Result.Name -contains $endpointName | Should Be $true + $Result.PSVersion -ge 5.1 | Should be $true + } + + It "Get-PSSessionConfiguration with Name parameter" { + + $Result = Get-PSSessionConfiguration -Name $endpointName + + $Result.Name | Should Be $endpointName + $Result.PSVersion -ge 5.1 | Should be $true + } + + It "Get-PSSessionConfiguration -Name with wildcard character" { + + $endpointWildcard = "powershell.*" + + $Result = Get-PSSessionConfiguration -Name $endpointWildcard + + $Result.Name -contains $endpointName | Should Be $true + $Result.PSVersion -ge 5.1 | Should be $true + } + + It "Get-PSSessionConfiguration -Name with Non-Existent session configuration" { + + try + { + Get-PSSessionConfiguration -Name "NonExistantSessionConfiguration" -ErrorAction Stop + throw "No Exception!" + } + catch + { + $_.FullyQualifiedErrorId | Should Be "Microsoft.PowerShell.Commands.WriteErrorException" + } + } + } + + Context "Validate Enable-PSSessionConfiguration and Disable-PSSessionConfiguration" { + + function VerifyEnableAndDisablePSSessionConfig { + param ( + [string] + $SessionConfigName, + + [string] + $ConfigFilePath, + + [Bool] + $InitialSessionStateEnabled, + + [Bool] + $FinalSessionStateEnabled, + + [string] + $TestDescription, + + [bool] + $EnablePSSessionConfig + ) + + It "$TestDescription" { + + RegisterNewConfiguration -Name $SessionConfigName -ConfigFilePath $ConfigFilePath -Enabled:$InitialSessionStateEnabled + + $TestConfigStateBeforeChange = (Get-PSSessionConfiguration -Name $SessionConfigName).Enabled + + if($EnablePSSessionConfig) + { + $isSkipNetworkCheck = $true + # TODO: Get-NetConnectionProfile is not available during typical PS Core deployments. Once it is, this check should be used. + #Get-NetConnectionProfile | Where-Object { $_.NetworkCategory -eq "Public" } | ForEach-Object { $isSkipNetworkCheck = $true } + Enable-PSSessionConfiguration -Name $SessionConfigName -NoServiceRestart -SkipNetworkProfileCheck:$isSkipNetworkCheck + } + else + { + Disable-PSSessionConfiguration -Name $SessionConfigName -NoServiceRestart + } + + $TestConfigStateAfterChange = (Get-PSSessionConfiguration -Name $SessionConfigName -ErrorAction SilentlyContinue).Enabled + + UnregisterPSSessionConfiguration -Name $SessionConfigName + + $TestConfigStateBeforeChange | Should be "$InitialSessionStateEnabled" + $TestConfigStateAfterChange | Should be "$FinalSessionStateEnabled" + } + } + + $TestData = @( + @{ + SessionConfigName = "TestDisablePSSessionConfig" + ConfigFilePath = $LocalConfigFilePath + InitialSessionStateEnabled = $true + FinalSessionStateEnabled = $false + TestDescription = "Validate Disable-Configuration cmdlet" + EnablePSSessionConfig = $false + } + + @{ + SessionConfigName = "TestEnablePSSessionConfig" + ConfigFilePath = $LocalConfigFilePath + InitialSessionStateEnabled = $false + FinalSessionStateEnabled = $true + TestDescription = "Validate Enable-Configuration cmdlet" + EnablePSSessionConfig = $true + } + ) + + foreach ($testcase in $testData) + { + VerifyEnableAndDisablePSSessionConfig @testcase + } + } + + Context "Validate Unregister-PSSessionConfiguration cmdlet" { + + BeforeEach { + Register-PSSessionConfiguration -Name "TestUnregisterPSSessionConfig" + } + + AfterAll { + if ($IsNotSkipped) + { + Unregister-PSSessionConfiguration -name "TestUnregisterPSSessionConfig" -ErrorAction SilentlyContinue | Out-Null + } + } + + function TestUnRegisterPSSsessionConfiguration { + + param ($Description, $SessionConfigName, $ExpectedOutput, $ExpectedError) + + It "$Description" { + + $Result = [PSObject] @{Output = $true ; Error = $null} + $Error.Clear() + try + { + $null = Unregister-PSSessionConfiguration -name $SessionConfigName -ErrorAction stop + } + catch + { + $Result.Error = $_.Exception + } + + if(-not $Result.Error) + { + $ValidEndpoints = [PSObject]@(Get-PSSessionConfiguration) + + foreach ($endpoint in $ValidEndpoints) + { + # Setting it to false means the unregister was unsuccessful + # and there is still an endpoint with name matching the one we wanted to remove. + if($endpoint.name -like $SessionConfigName) + { + $Result.Output = $false + break + } + } + } + else + { + $Result.Output = $false + } + + $Result.Output | Should Match $ExpectedOutput + $Result.Error | Should Match $ExpectedError + } + } + + $TestData = @( + @{ + Description = "Validate Unregister-PSSessionConfiguration with -name parameter" + SessionConfigName = "TestUnregisterPSSessionConfig" + ExpectedOutput = $true + ExpectedError = $null + } + @{ + Description = "Validate Unregister-PSSessionConfiguration with name having wildcard character" + SessionConfigName = "TestUnregister*" + ExpectedOutput = $true + ExpectedError = $null + } + @{ + Description = "Validate Unregister-PSSessionConfiguration for non-existant endpoint" + SessionConfigName = 'TestInvalidEndPoint' + ExpectedOutput = $false + ExpectedError = "No session configuration matches criteria `"TestInvalidEndPoint`"." + } + ) + + foreach ($TestCase in $TestData) + { + TestUnRegisterPSSsessionConfiguration @TestCase + } + } + } + + Describe "Validate Register-PSSessionConfiguration, Set-PSSessionConfiguration cmdlets" -Tags @("Feature", 'RequireAdminOnWindows') { + + BeforeAll { + if ($IsNotSkipped) + { + function ValidateRemoteEndpoint { + param ($TestSessionConfigName, $ScriptToExecute, $ExpectedOutput) + + $Result = [PSObject]@{Output= $null; Error = $null} + try + { + $sn = New-PSSession . -ConfigurationName $TestSessionConfigName -ErrorAction Stop + if($sn) + { + if($ScriptToExecute) + { + $Result.Output = invoke-command -Session $Sn -ScriptBlock { param ($scripttoExecute) Invoke-Expression $scripttoExecute} -ArgumentList $ScriptToExecute + } + else + { + $Result.Output = $true + } + } + else + { + throw "Unable to create session $TestSessionConfigName" + } + } + catch + { + $Result.Error = $_.Error.FullyQualifiedErrorId + } + finally + { + if ($sn) + { + Remove-PSSession $sn -ErrorAction SilentlyContinue | Out-Null + $sn = $null + } + } + $Result.Output | Should be $ExpectedOutput + $Result.Error | Should be $null + } + + # Create Test Startup Script + function CreateStartupScript { + $ScriptContent = @" +`$script:testvariable = "testValue" +"@ + + $TestScript = join-path $script:TestDir "StartupTestScript.ps1" + $null = Set-Content -path $TestScript -Value $ScriptContent + + return $TestScript + } + + # Create new Config File + function CreateTestConfigFile { + + $TestConfigFile = join-path $script:TestDir "TestConfigFile.pssc" + $null = New-PSSessionConfigurationFile -Path $TestConfigFile -SessionType Default + return $TestConfigFile + } + + function CreateTestModule { + $ScriptContent = @" +function IsTestModuleImported { +return `$true +} +Export-ModuleMember IsTestModuleImported +"@ + $TestModuleFileLoc = $script:TestDir + + if(-not (Test-path $TestModuleFileLoc)) + { + $null = New-Item -Path $TestModuleFileLoc -ItemType Directory -Force -ErrorAction Stop + } + + $TestModuleFile = join-path $TestModuleFileLoc "TestModule.psm1" + $null = Set-Content -path $TestModuleFile -Value $ScriptContent + + return $TestModuleFile + } + + function CreateTestAssembly { + $PscConfigDef = @" +using System; +using System.Management.Automation; +using System.Management.Automation.Runspaces; +using System.Management.Automation.Remoting; + +namespace PowershellTestConfigNamespace +{ + public sealed class PowershellTestConfig : PSSessionConfiguration + { + /// + /// + /// + /// + /// + public override InitialSessionState GetInitialSessionState(PSSenderInfo senderInfo) + { + return InitialSessionState.CreateDefault(); + } + + } +} +"@ + $script:SourceFile = join-path $script:TestAssemblyDir "PowershellTestConfig.cs" + $PscConfigDef | out-file $script:SourceFile -Encoding ascii -Force + $TestAssemblyName = "TestAssembly.dll" + $TestAssemblyPath = join-path $script:TestAssemblyDir $TestAssemblyName + Add-Type -path $script:SourceFile -OutputAssembly $TestAssemblyPath + return $TestAssemblyName + } + + $script:TestDir = join-path $TestDrive "Remoting" + if(-not (Test-Path $script:TestDir)) + { + $null = New-Item -path $script:TestDir -ItemType Directory + } + + $script:TestAssemblyDir = [System.IO.Path]::GetTempPath() + if(-not (Test-Path $script:TestAssemblyDir)) + { + $null = New-Item -path $script:TestAssemblyDir -ItemType Directory + } + + $LocalConfigFilePath = CreateTestConfigFile + $LocalStartupScriptPath = CreateStartupScript + $LocalTestModulePath = CreateTestModule + $LocalTestAssemblyName = CreateTestAssembly + $LocalTestDir = $script:TestDir + } + } + + AfterAll { + if ($IsNotSkipped) + { + Remove-Item $LocalTestDir -Recurse -Force -ErrorAction SilentlyContinue + } + } + + Context "Validate Register-PSSessionConfiguration" { + + BeforeAll { + if ($IsNotSkipped) + { + $TestSessionConfigName = "TestRegisterPSSesionConfig" + Unregister-PSSessionConfiguration -Name $TestSessionConfigName -Force -NoServiceRestart -ErrorAction SilentlyContinue + } + } + + AfterEach { + Unregister-PSSessionConfiguration -Name $TestSessionConfigName -Force -NoServiceRestart -ErrorAction SilentlyContinue + } + + It "Validate Register-PSSessionConfiguration -name -path" { + + $pssessionthreadoptions = "UseCurrentThread" + $psmaximumreceivedobjectsizemb = 20 + $psmaximumreceiveddatasizepercommandmb = 20 + $UseSharedProcess = $true + + Register-PSSessionConfiguration -Name $TestSessionConfigName -path $LocalConfigFilePath -MaximumReceivedObjectSizeMB $psmaximumreceivedobjectsizemb -MaximumReceivedDataSizePerCommandMB $psmaximumreceiveddatasizepercommandmb -UseSharedProcess:$UseSharedProcess -ThreadOptions $pssessionthreadoptions + $Result = [PSObject]@{Session = Get-PSSessionConfiguration -Name $TestSessionConfigName; Culture = (Get-Item WSMan:\localhost\Plugin\$endpointName\lang -ea SilentlyContinue).value} + + $Result.Session.Name | Should be $TestSessionConfigName + $Result.Session.SessionType | Should be "Default" + $Result.Session.PSVersion | Should be 6.0 + $Result.Session.Enabled | Should be $true + $Result.Session.lang | Should be $Result.Culture + $Result.Session.pssessionthreadoptions | Should be $pssessionthreadoptions + $Result.Session.psmaximumreceivedobjectsizemb | Should be $psmaximumreceivedobjectsizemb + $Result.Session.psmaximumreceiveddatasizepercommandmb | Should be $psmaximumreceiveddatasizepercommandmb + $Result.Session.UseSharedProcess | Should be $UseSharedProcess + } + + It "Validate Register-PSSessionConfiguration -startupscript parameter" { + + $null = Register-PSSessionConfiguration -Name $TestSessionConfigName -path $LocalConfigFilePath -StartupScript $LocalStartupScriptPath -Force + + ValidateRemoteEndpoint -TestSessionConfigName $TestSessionConfigName -ScriptToExecute "return `$script:testvariable" -ExpectedOutput "testValue" -ExpectedError $null + } + + + It "Validate Register-PSSessionConfiguration -AccessMode parameter" { + + $null = Register-PSSessionConfiguration -Name $TestSessionConfigName -path $LocalConfigFilePath -AccessMode Disabled -Force + + ValidateRemoteEndpoint -TestSessionConfigName $TestSessionConfigName -ScriptToExecute $null -ExpectedOutput $null -ExpectedError "RemoteConnectionDisallowed,PSSessionOpenFailed" + } + + + It "Validate Register-PSSessionConfiguration -ModulesToImport parameter" { + + $null = Register-PSSessionConfiguration -Name $TestSessionConfigName -ModulesToImport $LocalTestModulePath -Force + + ValidateRemoteEndpoint -TestSessionConfigName $TestSessionConfigName -ScriptToExecute "return IsTestModuleImported" -ExpectedOutput $true -ExpectedError $null + } + + It "Validate Register-PSSessionConfiguration with ApplicationBase, AssemblyName and ConfigurationTypeName parameter" { + + $null = Register-PSSessionConfiguration -Name $TestSessionConfigName -ApplicationBase $script:TestAssemblyDir -AssemblyName $LocalTestAssemblyName -ConfigurationTypeName "PowershellTestConfigNamespace.PowershellTestConfig" -force + + ValidateRemoteEndpoint -TestSessionConfigName $TestSessionConfigName -ScriptToExecute $null -ExpectedOutput $true -ExpectedError $null + } + } + + Context "Validate Set-PSSessionConfiguration" { + + BeforeAll { + if ($IsNotSkipped) + { + $TestSessionConfigName = "TestSetPSSesionConfig" + Unregister-PSSessionConfiguration -Name $TestSessionConfigName -Force -NoServiceRestart -ErrorAction SilentlyContinue + } + } + + AfterEach { + Unregister-PSSessionConfiguration -Name $TestSessionConfigName -Force -NoServiceRestart -ErrorAction SilentlyContinue + } + + BeforeEach { + Register-PSSessionConfiguration -Name $TestSessionConfigName + } + + It "Validate Set-PSSessionConfiguration -name -path -MaximumReceivedObjectSizeMB -MaximumReceivedDataSizePerCommandMB -UseSharedProcess -ThreadOptions parameters" { + + $pssessionthreadoptions = "UseCurrentThread" + $psmaximumreceivedobjectsizemb = 20 + $psmaximumreceiveddatasizepercommandmb = 20 + $UseSharedProcess = $true + + Set-PSSessionConfiguration -Name $TestSessionConfigName -MaximumReceivedObjectSizeMB $psmaximumreceivedobjectsizemb -MaximumReceivedDataSizePerCommandMB $psmaximumreceiveddatasizepercommandmb -UseSharedProcess:$UseSharedProcess -ThreadOptions $pssessionthreadoptions -NoServiceRestart + $Result = [PSObject]@{Session = (Get-PSSessionConfiguration -Name $TestSessionConfigName) ; Culture = (Get-Item WSMan:\localhost\Plugin\microsoft.powershell\lang -ea SilentlyContinue).value} + + $Result.Session.Name | Should be $TestSessionConfigName + $Result.Session.PSVersion | Should be 6.0 + $Result.Session.Enabled | Should be $true + $Result.Session.lang | Should be $result.Culture + $Result.Session.pssessionthreadoptions | Should be $pssessionthreadoptions + $Result.Session.psmaximumreceivedobjectsizemb | Should be $psmaximumreceivedobjectsizemb + $Result.Session.psmaximumreceiveddatasizepercommandmb | Should be $psmaximumreceiveddatasizepercommandmb + $Result.Session.UseSharedProcess | Should be $UseSharedProcess + } + + It "Validate Set-PSSessionConfiguration -startupscript parameter" { + + $null = Set-PSSessionConfiguration -Name $TestSessionConfigName -StartupScript $LocalStartupScriptPath + + ValidateRemoteEndpoint -TestSessionConfigName $TestSessionConfigName -ScriptToExecute "return `$script:testvariable" -ExpectedOutput "testValue" -ExpectedError $null + } + + It "Validate Set-PSSessionConfiguration -AccessMode parameter" { + + $null = Set-PSSessionConfiguration -Name $TestSessionConfigName -AccessMode Disabled + + ValidateRemoteEndpoint -TestSessionConfigName $TestSessionConfigName -ScriptToExecute $null -ExpectedOutput $null -ExpectedError "RemoteConnectionDisallowed,PSSessionOpenFailed" + } + + It "Validate Set-PSSessionConfiguration -ModulesToImport parameter" { + + $null = Set-PSSessionConfiguration -Name $TestSessionConfigName -ModulesToImport $LocalTestModulePath -Force + + ValidateRemoteEndpoint -TestSessionConfigName $TestSessionConfigName -ScriptToExecute "return IsTestModuleImported" -ExpectedOutput $true -ExpectedError $null + } + + It "Validate Set-PSSessionConfiguration with ApplicationBase, AssemblyName and ConfigurationTypeName parameter" { + + $null = Set-PSSessionConfiguration -Name $TestSessionConfigName -ApplicationBase $script:TestAssemblyDir -AssemblyName $LocalTestAssemblyName -ConfigurationTypeName "PowershellTestConfigNamespace.PowershellTestConfig" -force + + ValidateRemoteEndpoint -TestSessionConfigName $TestSessionConfigName -ScriptToExecute $null -ExpectedOutput $true -ExpectedError $null + } + } + } + } + finally + { + if ($endpointCreated) + { + Get-PSSessionConfiguration $endpointName -ErrorAction SilentlyContinue | Unregister-PSSessionConfiguration + } + } + + Describe "Basic tests for New-PSSessionConfigurationFile Cmdlet" -Tags @("CI", 'RequireAdminOnWindows') { + + It "Validate New-PSSessionConfigurationFile can successfully create a valid PSSessionConfigurationFile" { + + $configFilePath = join-path $TestDrive "SamplePSSessionConfigurationFile.pssc" + try + { + New-PSSessionConfigurationFile $configFilePath + $result = get-content $configFilePath | Out-String + } + finally + { + if(Test-Path $configFilePath){ Remove-Item $configFilePath -Force } + } + + $resultContent = invoke-expression ($result) + $resultContent.GetType().ToString() | Should Be "System.Collections.Hashtable" + + # The default created hashtable in the session configuration file would have the + # following keys which we are validating below. + $resultContent.ContainsKey("SessionType") -and $resultContent.ContainsKey("SchemaVersion") -and $resultContent.ContainsKey("Guid") -and $resultContent.ContainsKey("Author") | Should Be $true + } + } + + Describe "Feature tests for New-PSSessionConfigurationFile Cmdlet" -Tags @("Feature", 'RequireAdminOnWindows') { + + It "Validate FullyQualifiedErrorId from New-PSSessionConfigurationFile when invalid path is provided as input" { + + try + { + $filePath = "cert:\foo.pssc" + New-PSSessionConfigurationFile $filePath + throw "No Exception!" + } + catch + { + $_.FullyQualifiedErrorId | Should Be "InvalidPSSessionConfigurationFilePath,Microsoft.PowerShell.Commands.NewPSSessionConfigurationFileCommand" + } + } + } + + Describe "Test suite for Test-PSSessionConfigurationFile Cmdlet" -Tags @("CI", 'RequireAdminOnWindows') { + + BeforeAll { + if ($IsNotSkipped) + { + $parmMap = @{ + # values for PSSessionConfigFile + PowerShellVersion = '3.0' + SessionType = 'Default' + Author = 'User' + CompanyName = 'Microsoft Corporation' + Copyright = 'Copyright (c) 2011 Microsoft Corporation. All rights reserved.' + Description = 'This is a sample session configuration file.' + GUID = '73cba863-aa49-4cbf-9917-269ddcf2b1e3' + SchemaVersion = '1.0.0.0' + + # The scope of the test is to validate that a valid SessionConfigurationFile can be validated + # The test does not register the session configuration from the created session configuration file. + # The SCRATCH location is not validated. + EnvironmentVariables = @{ + PSModulePath = '$Env:PSModulePath + ";$env:SystemDrive\ProgramData"'; + SCRATCH = "\\SomeValidRemoteShare\SharedLocation" + } + + # The scope of the test is to validate that a valid SessionConfigurationFile can be validated + # The test does not register the session configuration from the created session configuration file. + # The AssembliesToLoad are not loaded by this test. The Test only validates that the supplied data + # is used to create a valid Session configuration file. + AssembliesToLoad = 'SomeValidBinary.dll' + + # The same explanation as above holds good here. + ModulesToImport = 'SomeValidModule' + AliasDefinitions = @( + @{ + Name = "gh"; + Value = "Get-Help"; + Description = "Gets the help"; + Options = "AllScope"; + }, + @{ + Name = "sh"; + Value = "Save-Help"; + Description = "Saves the help"; + Options = "Private"; + }, + @{ + Name = "uh"; + Value = "Update-Help"; + Description = "Updates the help"; + Options = "ReadOnly"; + } + ) + FunctionDefinitions=@( + @{ + Name = "sysmodules"; + ScriptBlock = 'pushd $pshome\Modules'; + Options = "AllScope"; + }, + @{ + Name = "mymodules"; + ScriptBlock = 'pushd $home\Documents\WindowsPowerShell\Modules'; + Options = "ReadOnly"; + } + ) + VariableDefinitions = @( + @{ + Name = "WarningPreference"; + Value = "SilentlyContinue"; + }, + @{ + Name = "datahome"; + Value = "\\fileserver\share\data"; + }, + @{ + Name = "allusershome"; + Value = '$env:ProgramData' + } + ) + + # The scope of the test is to validate that a valid SessionConfigurationFile can be validated + # The test does not register the session configuration from the created session configuration file. + # The existance of the files supplied as input to TypesToProcess, FormatsToProcess, ScriptsToProcess + # are not validated while creating a valid session configurtation file. + # The Test only validates that the supplied data can be successfully used to create a valid Session configuration file. + TypesToProcess = '$env:SystemDrive\SampleTypesFile.ps1xml' + FormatsToProcess = '$env:SystemDrive\SampleFormatsFile.ps1xml' + ScriptsToProcess = '$env:SystemDrive\SampleScript.ps1' + VisibleAliases = "c*","g*","i*","s*" + VisibleCmdlets = "c*","get*","i*","set*" + VisibleFunctions = "*" + VisibleProviders = 'FileSystem','Function','Registry','Variable' + VisibleVariables = "*" + LanguageMode = "RestrictedLanguage" + ExecutionPolicy = "AllSigned" + } + } + } + + It "Validate FullyQualifiedErrorId from Test-PSSessionConfigurationFile when invalid path is provided as input" { + + try + { + Test-PSSessionConfigurationFile "cert:\foo.pssc" -ErrorAction Stop + throw "No Exception!" + } + catch + { + $_.FullyQualifiedErrorId | Should Be "PSSessionConfigurationFileNotFound,Microsoft.PowerShell.Commands.TestPSSessionConfigurationFileCommand" + } + } + + It "Validate FullyQualifiedErrorId from Test-PSSessionConfigurationFile when an invalid pssc file is provided as input and -Verbose parameter is specified" { + + $configFilePath = join-path $TestDrive "SamplePSSessionConfigurationFile.pssc" + "InvalidData" | Out-File $configFilePath + + Test-PSSessionConfigurationFile $configFilePath -Verbose -ErrorAction Stop | Should Be $false + } + + It "Test case verifies that the generated config file passes validation" { + + # Path the config file + $configFilePath = join-path $TestDrive "SamplePSSessionConfigurationFile.pssc" + + $updatedFunctionDefn = @() + foreach($currentDefination in $parmMap.FunctionDefinitions) + { + $createdFunctionDefn = @{} + foreach($currentDefinationKey in $currentDefination.Keys) + { + if($currentDefinationKey -eq "ScriptBlock") + { + $value = [ScriptBlock]::Create($currentDefination[$currentDefinationKey]) + } + else + { + $value = $currentDefination[$currentDefinationKey] + } + $createdFunctionDefn.Add($currentDefinationKey, $value) + } + $updatedFunctionDefn += $createdFunctionDefn + } + + $updatedVariableDefn = @() + foreach($currentDefination in $parmMap.VariableDefinitions) + { + $createdVariableDefn = @{} + foreach($currentDefinationKey in $currentDefination.Keys) + { + $createdVariableDefn.Add($currentDefinationKey, $currentDefination[$currentDefinationKey]) + } + $updatedVariableDefn += $createdVariableDefn + } + + try + { + # Create Config file + New-PSSessionConfigurationFile ` + -Path $configFilePath ` + -SchemaVersion $parmMap.SchemaVersion ` + -Author $parmMap.Author ` + -CompanyName $parmMap.CompanyName ` + -Copyright $parmMap.Copyright ` + -Description $parmMap.Description ` + -PowerShellVersion $parmMap.PowerShellVersion ` + -SessionType $parmMap.SessionType ` + -ModulesToImport $parmMap.ModulesToImport ` + -AssembliesToLoad $parmMap.AssembliesToLoad ` + -VisibleAliases $parmMap.VisibleAliases ` + -VisibleCmdlets $parmMap.VisibleCmdlets ` + -VisibleFunctions $parmMap.VisibleFunctions ` + -VisibleProviders $parmMap.VisibleProviders ` + -AliasDefinitions $parmMap.AliasDefinitions ` + -FunctionDefinitions $updatedFunctionDefn ` + -VariableDefinitions $updatedVariableDefn ` + -EnvironmentVariables $parmMap.EnvironmentVariables ` + -TypesToProcess $parmMap.TypesToProcess ` + -FormatsToProcess $parmMap.FormatsToProcess ` + -LanguageMode $parmMap.LanguageMode ` + -ExecutionPolicy $parmMap.ExecutionPolicy ` + -ScriptsToProcess $parmMap.ScriptsToProcess ` + -GUID $parmMap.GUID + + # Verify the generated config file using the Test-PSSessionConfigurationFile + $result = Test-PSSessionConfigurationFile -Path $configFilePath -Verbose + } + + finally + { + if(Test-Path $configFilePath) + { + Remove-Item $configFilePath -Force + } + } + + $result | Should Be $true + } + } +} +finally +{ + $global:PSDefaultParameterValues = $originalDefaultParameterValues +} + diff --git a/test/powershell/engine/DefaultCommands.Tests.ps1 b/test/powershell/engine/DefaultCommands.Tests.ps1 index 42760abf08c..50ddb81b4d6 100644 --- a/test/powershell/engine/DefaultCommands.Tests.ps1 +++ b/test/powershell/engine/DefaultCommands.Tests.ps1 @@ -206,7 +206,7 @@ Describe "Verify approved aliases list" -Tags "CI" { "Cmdlet", "Debug-Runspace", , $($FullCLR -or $CoreWindows -or $CoreUnix) "Cmdlet", "Disable-ComputerRestore", , $($FullCLR ) "Cmdlet", "Disable-PSBreakpoint", , $($FullCLR -or $CoreWindows -or $CoreUnix) -"Cmdlet", "Disable-PSRemoting", , $($FullCLR ) +"Cmdlet", "Disable-PSRemoting", , $($FullCLR -or $CoreWindows ) "Cmdlet", "Disable-PSSessionConfiguration", , $($FullCLR -or $CoreWindows -or $CoreUnix) "Cmdlet", "Disable-RunspaceDebug", , $($FullCLR -or $CoreWindows -or $CoreUnix) "Cmdlet", "Disable-WSManCredSSP", , $($FullCLR -or $CoreWindows ) @@ -214,7 +214,7 @@ Describe "Verify approved aliases list" -Tags "CI" { "Cmdlet", "Disconnect-WSMan", , $($FullCLR -or $CoreWindows ) "Cmdlet", "Enable-ComputerRestore", , $($FullCLR ) "Cmdlet", "Enable-PSBreakpoint", , $($FullCLR -or $CoreWindows -or $CoreUnix) -"Cmdlet", "Enable-PSRemoting", , $($FullCLR ) +"Cmdlet", "Enable-PSRemoting", , $($FullCLR -or $CoreWindows ) "Cmdlet", "Enable-PSSessionConfiguration", , $($FullCLR -or $CoreWindows -or $CoreUnix) "Cmdlet", "Enable-RunspaceDebug", , $($FullCLR -or $CoreWindows -or $CoreUnix) "Cmdlet", "Enable-WSManCredSSP", , $($FullCLR -or $CoreWindows )