Skip to content

Commit

Permalink
Merge pull request PowerShell#2089 from 0xfeeddeadbeef/master
Browse files Browse the repository at this point in the history
Use prefixed QNames in XPath expressions used by all implementations of ICmdletProviderSupportsHelp interface
  • Loading branch information
vors committed Sep 13, 2016
2 parents 79ddafc + db33020 commit 81a598f
Show file tree
Hide file tree
Showing 10 changed files with 133 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3146,11 +3146,14 @@ string ICmdletProviderSupportsHelp.GetHelpMaml(string helpItemName, string path)
#if !CORECLR
settings.XmlResolver = null;
#endif
XmlReader reader = XmlReader.Create(fullHelpPath, settings);
document.Load(reader);
using (XmlReader reader = XmlReader.Create(fullHelpPath, settings))
{
document.Load(reader);
}

// Add the "command" namespace from the MAML schema
// Add "msh" and "command" namespaces from the MAML schema
XmlNamespaceManager nsMgr = new XmlNamespaceManager(document.NameTable);
nsMgr.AddNamespace("msh", HelpCommentsParser.mshURI);
nsMgr.AddNamespace("command", HelpCommentsParser.commandURI);


Expand Down
31 changes: 20 additions & 11 deletions src/Microsoft.WSMan.Management/ConfigProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -95,9 +95,10 @@ string ICmdletProviderSupportsHelp.GetHelpMaml(string helpItemName, string path)
string muiDirectory = Path.Combine(providerBase, culture.Name);
if (Directory.Exists(muiDirectory))
{
if (File.Exists(muiDirectory + "\\" + this.ProviderInfo.HelpFile))
string supposedHelpFile = Path.Combine(muiDirectory, this.ProviderInfo.HelpFile);
if (File.Exists(supposedHelpFile))
{
helpFile = muiDirectory + "\\" + this.ProviderInfo.HelpFile;
helpFile = supposedHelpFile;
break;
}
}
Expand All @@ -112,10 +113,15 @@ string ICmdletProviderSupportsHelp.GetHelpMaml(string helpItemName, string path)

try
{
//XmlDocument in CoreCLR takes a stream instead of a string for loading xml
byte[] byteArray = Encoding.UTF8.GetBytes(helpFile);
MemoryStream stream = new MemoryStream(byteArray);
document.Load(stream);
//XmlDocument in CoreCLR does not have file path parameter, use XmlReader
XmlReaderSettings readerSettings = new XmlReaderSettings();
#if !CORECLR
readerSettings.XmlResolver = null;
#endif
using (XmlReader reader = XmlReader.Create(helpFile, readerSettings))
{
document.Load(reader);
}
}
catch(XmlException)
{
Expand All @@ -141,17 +147,20 @@ string ICmdletProviderSupportsHelp.GetHelpMaml(string helpItemName, string path)
{
return String.Empty;
}
// Add the "command" namespace from the MAML schema

// Add the "msh" and "command" namespaces from the MAML schema
XmlNamespaceManager nsMgr = new XmlNamespaceManager(document.NameTable);
// XPath 1.0 associates empty prefix with "null" namespace; must use non-empty prefix for default namespace.
// This will not work: nsMgr.AddNamespace("", "http://msh");
nsMgr.AddNamespace("msh", "http://msh");
nsMgr.AddNamespace("command", "http://schemas.microsoft.com/maml/dev/command/2004/10");

// Split the help item name into verb and noun
string verb = helpItemName.Split('-')[0];
string noun = helpItemName.Substring(helpItemName.IndexOf('-')+1);
string noun = helpItemName.Substring(helpItemName.IndexOf('-') + 1);

//Compose XPath query to select the appropriate node based on the verb,noun and ID
string xpathQuery = "/helpItems/providerHelp/CmdletHelpPaths/CmdletHelpPath[@ID='" + child + "']/command:command/command:details[command:verb='" + verb + "' and command:noun='" + noun + "']";
//Compose XPath query to select the appropriate node based on the verb, noun and id
string xpathQuery = "/msh:helpItems/msh:providerHelp/msh:CmdletHelpPaths/msh:CmdletHelpPath[@id='" + child + "' or @ID='" + child + "']/command:command/command:details[command:verb='" + verb + "' and command:noun='" + noun + "']";

// Execute the XPath query and if the command was found, return its MAML snippet
XmlNode result = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6149,7 +6149,8 @@ private static bool HasDefaultConstructor(Type type)

private static string GetHelpFile(string assemblyPath)
{
return Path.GetFileName(assemblyPath) + StringLiterals.HelpFileExtension;
// Help files exist only for original module assemblies, not for generated Ngen binaries
return Path.GetFileName(assemblyPath).Replace(".ni.dll", ".dll") + StringLiterals.HelpFileExtension;
}

private static PSTraceSource s_PSSnapInTracer = PSTraceSource.GetTracer("PSSnapInLoadUnload", "Loading and unloading mshsnapins", false);
Expand Down
6 changes: 5 additions & 1 deletion src/System.Management.Automation/help/HelpCommentsParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,13 +66,17 @@ private HelpCommentsParser(CommandInfo commandInfo, List<string> parameterDescri
private string _commandName;
private List<string> _parameterDescriptions;
private XmlDocument _doc;
internal static readonly string mshURI = "http://msh";
internal static readonly string mamlURI = "http://schemas.microsoft.com/maml/2004/10";
internal static readonly string commandURI = "http://schemas.microsoft.com/maml/dev/command/2004/10";
internal static readonly string devURI = "http://schemas.microsoft.com/maml/dev/2004/10";
private const string directive = @"^\s*\.(\w+)(\s+(\S.*))?\s*$";
private const string blankline = @"^\s*$";
// Although "http://msh" is the default namespace, it still must be explicitly qualified with non-empty prefix,
// because XPath 1.0 will associate empty prefix with "null" namespace (not with "default") and query will fail.
// See: http://www.w3.org/TR/1999/REC-xpath-19991116/#node-tests
internal static readonly string ProviderHelpCommandXPath =
"/helpItems/providerHelp/CmdletHelpPaths/CmdletHelpPath{0}/command:command[command:details/command:verb='{1}' and command:details/command:noun='{2}']";
"/msh:helpItems/msh:providerHelp/msh:CmdletHelpPaths/msh:CmdletHelpPath{0}/command:command[command:details/command:verb='{1}' and command:details/command:noun='{2}']";

private void DetermineParameterDescriptions()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -272,8 +272,9 @@ public string GetHelpMaml(string helpItemName, string path)
reader = XmlReader.Create(fullHelpPath, settings);
document.Load(reader);

// Add the "command" namespace from the MAML schema
// Add "msh" and "command" namespaces from the MAML schema
XmlNamespaceManager nsMgr = new XmlNamespaceManager(document.NameTable);
nsMgr.AddNamespace("msh", HelpCommentsParser.mshURI);
nsMgr.AddNamespace("command", HelpCommentsParser.commandURI);

// Compose XPath query to select the appropriate node based on the cmdlet
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<HelpInfo xmlns="http://schemas.microsoft.com/powershell/help/2010/05">
<HelpContentURI>http://go.microsoft.com/fwlink/?linkid=390786</HelpContentURI>
<SupportedUICultures>
<UICulture>
<UICultureName>en-US</UICultureName>
<UICultureVersion>5.0.5.0</UICultureVersion>
</UICulture>
</SupportedUICultures>
</HelpInfo>
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<HelpInfo xmlns="http://schemas.microsoft.com/powershell/help/2010/05">
<HelpContentURI>http://go.microsoft.com/fwlink/?linkid=390788</HelpContentURI>
<SupportedUICultures>
<UICulture>
<UICultureName>en-US</UICultureName>
<UICultureVersion>5.0.4.0</UICultureVersion>
</UICulture>
</SupportedUICultures>
</HelpInfo>
Binary file not shown.
88 changes: 78 additions & 10 deletions test/powershell/engine/HelpSystem.Tests.ps1
Original file line number Diff line number Diff line change
@@ -1,31 +1,40 @@
#
# Validates Get-Help for cmdlets in Microsoft.PowerShell.Core.

function RunTestCase
function UpdateHelpFromLocalContentPath
{
param ([string]$tag = "CI")

$moduleName = "Microsoft.PowerShell.Core"
param ([string]$ModuleName, [string]$Tag = 'CI')

# Update-Help is not yet supported on non-Windows platforms;
# currently it is using Windows Cabinet API (cabinet.dll) internally
if ($IsWindows)
{
if ($tag -eq "CI")
if ($Tag -eq 'CI')
{
$helpContentPath = join-path $PSScriptRoot "HelpContent"
$helpContentPath = Join-Path $PSScriptRoot "HelpContent"
$helpFiles = @(Get-ChildItem "$helpContentPath\*" -ea SilentlyContinue)

if ($helpFiles.Count -eq 0)
{
throw "unable to find help content at '$helpContentPath'"
throw "Unable to find help content at '$helpContentPath'"
}
Update-Help -Module $moduleName -SourcePath $helpContentPath -Force -ErrorAction Stop -Verbose
}

Update-Help -Module $ModuleName -SourcePath $helpContentPath -Force -ErrorAction Stop -Verbose
}
else
{
Update-Help -Module $moduleName -Force -Verbose -ErrorAction Stop
Update-Help -Module $ModuleName -Force -Verbose -ErrorAction Stop
}
}
}

function RunTestCase
{
param ([string]$tag = "CI")

$moduleName = "Microsoft.PowerShell.Core"

UpdateHelpFromLocalContentPath $moduleName $tag

$cmdlets = get-command -module $moduleName

Expand Down Expand Up @@ -65,3 +74,62 @@ Describe "Validate Get-Help for all cmdlets in 'Microsoft.PowerShell.Core'" -Tag

RunTestCase -tag "Feature"
}

Describe "Validate that Get-Help returns provider-specific help" -Tags "CI" {
BeforeAll {
$namespaces = @{
command = 'http://schemas.microsoft.com/maml/dev/command/2004/10'
dev = 'http://schemas.microsoft.com/maml/dev/2004/10'
maml = 'http://schemas.microsoft.com/maml/2004/10'
msh = 'http://msh'
}

# Currently these test cases are verified only on Windows, because
# - WSMan:\ and Cert:\ providers are not yet supported on non-Windows platforms.
# - Update-Help is not yet supported on non-Windows platforms; it is using Windows Cabinet API (cabinet.dll) internally.
$testCases = @(
@{
helpFile = "$PSHOME\$([Globalization.CultureInfo]::CurrentUICulture)\System.Management.Automation.dll-help.xml"
path = "$PSHOME"
helpContext = "[@id='FileSystem' or @ID='FileSystem']"
verb = 'Add'
noun = 'Content'
},
@{
helpFile = "$PSHOME\$([Globalization.CultureInfo]::CurrentUICulture)\Microsoft.PowerShell.Security.dll-help.xml"
path = 'Cert:\'
helpContext = $null # CertificateProvider uses only verb and noun in XPath query
verb = 'New'
noun = 'Item'
},
@{
helpFile = "$PSHOME\$([Globalization.CultureInfo]::CurrentUICulture)\Microsoft.WSMan.Management.dll-help.xml"
path = 'WSMan:\localhost\ClientCertificate'
helpContext = "[@id='ClientCertificate' or @ID='ClientCertificate']"
verb = 'New'
noun = 'Item'
}
)

UpdateHelpFromLocalContentPath -ModuleName 'Microsoft.PowerShell.Core' -Tag 'CI'
UpdateHelpFromLocalContentPath -ModuleName 'Microsoft.PowerShell.Security' -Tag 'CI'
UpdateHelpFromLocalContentPath -ModuleName 'Microsoft.WSMan.Management' -Tag 'CI'
}

It -Skip:(-not $IsWindows) "shows contextual help when Get-Help is invoked for provider-specific path (Get-Help -Name <verb>-<noun> -Path <path>)" -TestCases $testCases {
param($helpFile, $path, $helpContext, $verb, $noun)

# Path should exist or else Get-Help will fallback to default help text
$path | Should Exist

$xpath = "/msh:helpItems/msh:providerHelp/msh:CmdletHelpPaths/msh:CmdletHelpPath$helpContext/command:command/command:details[command:verb='$verb' and command:noun='$noun']"
$helpXmlNode = Select-Xml -Path $helpFile -XPath $xpath -Namespace $namespaces | Select-Object -ExpandProperty Node

# Synopsis comes from command:command/command:details/maml:description
$expected = Get-Help -Name "$verb-$noun" -Path $path | Select-Object -ExpandProperty Synopsis

# System.Management.Automation.ProviderContext.GetProviderSpecificHelpInfo ignores extra whitespace, line breaks and
# comments when loading help XML, but Select-Xml can not; use BeLikeExactly operator to omit trailing line breaks:
$helpXmlNode.description.para -clike "$expected*" | Should Be $true
}
}

0 comments on commit 81a598f

Please sign in to comment.