Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -489,37 +489,6 @@ private static string GetSerializedCommandScript()
@"Remove-Item -Path 'function:\PSGetSerializedShowCommandInfo' -Force");
}

/// <summary>
/// Gets the command to be run to in order to import a module and refresh the command data.
/// </summary>
/// <param name="module">Module we want to import.</param>
/// <param name="isRemoteRunspace">Boolean flag determining whether Show-Command is queried in the local or remote runspace scenario.</param>
/// <param name="isFirstChance">Boolean flag to indicate that it is the second attempt to query Show-Command data.</param>
/// <returns>The command to be run to in order to import a module and refresh the command data.</returns>
internal static string GetImportModuleCommand(string module, bool isRemoteRunspace = false, bool isFirstChance = true)
{
string scriptBase = "Import-Module " + ShowCommandHelper.SingleQuote(module);

if (isRemoteRunspace)
{
if (isFirstChance)
{
scriptBase += ";@(Get-Command " + ShowCommandHelper.CommandTypeSegment + @" -ShowCommandInfo )";
}
else
{
scriptBase += GetSerializedCommandScript();
}
}
else
{
scriptBase += ";@(Get-Command " + ShowCommandHelper.CommandTypeSegment + ")";
}

scriptBase += ShowCommandHelper.GetGetModuleSuffix();
return scriptBase;
}

/// <summary>
/// Gets the command to be run in order to show help for a command.
/// </summary>
Expand Down
20 changes: 19 additions & 1 deletion src/System.Management.Automation/engine/Utils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1256,7 +1256,8 @@ internal static bool PathIsDevicePath(string path)
#if UNIX
return false;
#else
return path.StartsWith(@"\\.\") || path.StartsWith(@"\\?\");
// device paths can be network paths, we would need windows to parse it.
return path.StartsWith(@"\\.\") || path.StartsWith(@"\\?\") || path.StartsWith(@"\\;");
#endif
}

Expand Down Expand Up @@ -1523,6 +1524,23 @@ internal static string DisplayHumanReadableFileSize(long bytes)
_ => $"0 Bytes",
};
}

/// <summary>
/// Returns true if the current session is restricted (JEA or similar sessions)
/// </summary>
/// <param name="context">ExecutionContext.</param>
/// <returns>True if the session is restricted.</returns>
internal static bool IsSessionRestricted(ExecutionContext context)
{
CmdletInfo cmdletInfo = context.SessionState.InvokeCommand.GetCmdlet("Microsoft.PowerShell.Core\\Import-Module");
// if import-module is visible, then the session is not restricted,
// because the user can load arbitrary code.
if (cmdletInfo != null && cmdletInfo.Visibility == SessionStateEntryVisibility.Public)
{
return false;
}
return true;
}
}
}

Expand Down
13 changes: 12 additions & 1 deletion src/System.Management.Automation/help/HelpCommands.cs
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,17 @@ protected override void BeginProcessing()
/// </summary>
protected override void ProcessRecord()
{
#if !UNIX
string fileSystemPath = SessionState.Path.GetUnresolvedProviderPathFromPSPath(this.Name);
string normalizedName = FileSystemProvider.NormalizePath(fileSystemPath);
// In a restricted session, do not allow help on network paths or device paths, because device paths can be used to bypass the restrictions.
if (Utils.IsSessionRestricted(this.Context) && (FileSystemProvider.PathIsNetworkPath(normalizedName) || Utils.PathIsDevicePath(normalizedName))) {
Exception e = new ArgumentException(HelpErrors.NoNetworkCommands, "Name");
ErrorRecord errorRecord = new ErrorRecord(e, "CommandNameNotAllowed", ErrorCategory.InvalidArgument, null);
this.ThrowTerminatingError(errorRecord);
}
#endif

HelpSystem helpSystem = this.Context.HelpSystem;
try
{
Expand Down Expand Up @@ -504,7 +515,7 @@ private void GetAndWriteParameterInfo(HelpInfo helpInfo)
}

/// <summary>
/// Validates input parameters.
/// Validates input parameters.
/// </summary>
/// <param name="cat">Category specified by the user.</param>
/// <exception cref="ArgumentException">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ public FileSystemProvider()
/// <returns>
/// The path with all / normalized to \
/// </returns>
private static string NormalizePath(string path)
internal static string NormalizePath(string path)
{
return GetCorrectCasedPath(path.Replace(StringLiterals.AlternatePathSeparator, StringLiterals.DefaultPathSeparator));
}
Expand Down
3 changes: 3 additions & 0 deletions src/System.Management.Automation/resources/HelpErrors.resx
Original file line number Diff line number Diff line change
Expand Up @@ -187,4 +187,7 @@ To update these Help topics, start PowerShell by using the "Run as Administrator
<data name="CircularDependencyInHelpForwarding" xml:space="preserve">
<value>ForwardHelpTargetName cannot refer to the function itself.</value>
</data>
<data name="NoNetworkCommands" xml:space="preserve">
<value>Cannot get help from a network location when in a restricted session.</value>
</data>
</root>
17 changes: 17 additions & 0 deletions test/powershell/engine/Help/HelpSystem.OnlineHelp.Tests.ps1
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.

Import-Module HelpersCommon

Describe 'Online help tests for PowerShell Cmdlets' -Tags "Feature" {

# The csv files (V2Cmdlets.csv and V3Cmdlets.csv) contain a list of cmdlets and expected HelpURIs.
Expand Down Expand Up @@ -61,3 +63,18 @@ Describe 'Get-Help -Online is not supported on Nano Server and IoT' -Tags "CI" {
{ Get-Help Get-Help -Online } | Should -Throw -ErrorId "InvalidOperation,Microsoft.PowerShell.Commands.GetHelpCommand"
}
}

Describe 'Get-Help should throw on network paths' -Tags "CI" {
BeforeAll {
$script:skipTest = -not $IsWindows
}

It "Get-Help should throw not on <command>" -Skip:$skipTest -TestCases (Get-HelpNetworkTestCases -PositiveCases) {
param(
$Command,
$ExpectedError
)

{ Get-Help -Name $Command } | Should -Not -Throw
}
}
30 changes: 29 additions & 1 deletion test/powershell/engine/Remoting/RemoteSession.Basic.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,6 @@ Describe "JEA session Transcript script test" -Tag @("Feature", 'RequireAdminOnW
Unregister-PSSessionConfiguration -Name JEA -Force -ErrorAction SilentlyContinue
}
}

}

Describe "JEA session Get-Help test" -Tag @("CI", 'RequireAdminOnWindows') {
Expand Down Expand Up @@ -155,6 +154,34 @@ Describe "JEA session Get-Help test" -Tag @("CI", 'RequireAdminOnWindows') {
Remove-Item $RoleCapDirectory -Recurse -Force -ErrorAction SilentlyContinue
}
}

It "Get-Help should throw <ExpectedError> on <command>" -TestCases (Get-HelpNetworkTestCases) {
param(
$Command,
$ExpectedError
)

[string] $RoleCapDirectory = (New-Item -Path "$TestDrive\RoleCapability" -ItemType Directory -Force).FullName
[string] $PSSessionConfigFile = "$RoleCapDirectory\TestConfig.pssc"
$configurationName = 'RestrictedWithNoGetHelpProxy'
try
{
New-PSSessionConfigurationFile -Path $PSSessionConfigFile `
-SessionType Empty `
-LanguageMode NoLanguage `
-ModulesToImport 'Microsoft.PowerShell.Utility', 'Microsoft.PowerShell.Core' `
-VisibleCmdlets 'Get-command', 'measure-object', 'select-object', 'enter-pssession', 'get-formatdata', 'out-default', 'out-file', 'exit-pssession', 'get-help'
Register-PSSessionConfiguration -Name $configurationName -Path $PSSessionConfigFile -Force -ErrorAction SilentlyContinue
$scriptBlock = [scriptblock]::Create("Get-Help -Name $Command")
{Invoke-Command -ConfigurationName $configurationName -ComputerName localhost -ScriptBlock $scriptBlock -ErrorAction Stop} |
Should -Throw -ErrorId $ExpectedError
}
finally
{
Unregister-PSSessionConfiguration -Name $configurationName -Force -ErrorAction SilentlyContinue
Remove-Item $RoleCapDirectory -Recurse -Force -ErrorAction SilentlyContinue
}
}
}

Describe "Remoting loopback tests" -Tags @('CI', 'RequireAdminOnWindows') {
Expand Down Expand Up @@ -358,6 +385,7 @@ Describe "Remoting loopback tests" -Tags @('CI', 'RequireAdminOnWindows') {
$session = New-RemoteSession -ConfigurationName $endPoint
try {
$result = Invoke-Command -Session $session -ScriptBlock { $Host.Version }
Write-Verbose "host version: $result" -Verbose
$result | Should -Be $PSVersionTable.PSVersion
}
finally {
Expand Down
1 change: 1 addition & 0 deletions test/tools/Modules/HelpersCommon/HelpersCommon.psd1
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ FunctionsToExport = @(
'Test-PSDefaultParameterValue'
'Push-DefaultParameterValueStack'
'Pop-DefaultParameterValueStack'
'Get-HelpNetworkTestCases'
)

CmdletsToExport= @()
Expand Down
84 changes: 84 additions & 0 deletions test/tools/Modules/HelpersCommon/HelpersCommon.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -533,3 +533,87 @@ function Pop-DefaultParameterValueStack {
return $false
}
}

function Get-HelpNetworkTestCases
{
param(
[switch]
$PositiveCases
)
# .NET doesn't consider these path rooted and we won't go to the network:
# \\?
# \\.
# \??

# Command discovery does not follow symlinks to network locations for module qualified paths
$networkBlockedError = "CommandNameNotAllowed,Microsoft.PowerShell.Commands.GetHelpCommand"
$scriptBlockedError = "ScriptsNotAllowed"

$formats = @(
'//{0}/share/{1}'
'\\{0}\share\{1}'
'//{0}\share/{1}'
'Microsoft.PowerShell.Core\filesystem:://{0}/share/{1}'
)

if (!$PositiveCases) {
$formats += 'filesystem:://{0}/share/{1}'
}

$moduleQualifiedCommand = 'test.dll\fakecommand'
$lanManFormat = @(
'//;LanmanRedirector/{0}/share/{1}'
)

$hosts = @(
'fakehost'
'fakehost.pstest'
)

$commands = @(
'test.ps1'
'test.dll'
$moduleQualifiedCommand
)

$variants = @()
$cases = @()
foreach($command in $commands) {
$hostName = $hosts[0]
$format = $formats[0]
$cases += @{
Command = $format -f $hostName, $command
ExpectedError = $networkBlockedError
}
}

foreach($hostName in $hosts) {
# chose the format with backslashes(\) to match the host with blackslashes
$format = $formats[1]
$command = $commands[0]
$cases += @{
Command = $format -f $hostName, $command
ExpectedError = $networkBlockedError
}
}
foreach($format in $formats) {
$hostName = $hosts[0]
$command = $commands[0]
$cases += @{
Command = $format -f $hostName, $command
ExpectedError = $networkBlockedError
}
}

foreach($format in $lanManFormat) {
$hostName = $hosts[0]
$command = $moduleQualifiedCommand
$cases += @{
Command = $format -f $hostName, $command
ExpectedError = $scriptBlockedError
}
}

return $cases | Sort-Object -Property ExpectedError, Command -Unique
}