PowerShell 3.0 introduced updateable help feature because of which there is no in-box help. We need to use Update-Help and Save-Help cmdlets to update the help content from Microsoft download sites.
Now, this is a great feature to help people get up-to-date help from Microsoft. However, there are a couple of complaints problems I had.
- I never got Save-Help to work behind a proxy.
- Most of the machines in the lab setup have no Internet connectivity and/or always isolated from outside world. So, to get the help updated on these systems, I need to try a lot of workarounds like copying the module files to another system that has Internet connectivity and then using Save-Help to get the help content to a removable drive and then transport it back to the lab. There are quite a few new modules in Windows 8 Server and this method won’t scale.
So, I decided to see how exactly Save-Help works. It is simple!
If you look at the module manifest in PowerShell 3.0, each module has a HelpInfoUri which points to an online source to download the content. But, this is not exactly the same URL you can use to download the help content. It essentially redirects to a download.microsoft.com URL and then Save-Help uses its magic to download the right files. All this magic is in the module manifest itself. This will become clear if you look at the files that are downloaded using Save-Help.
It should be clear from this that there is a standard pattern in the file naming. So, if we know the correct URL from the HelpInfoUri from the module manifest, we know what files to download and from where.
So, the following script uses this logic to get the files from Internet and at the same time, resolves the two issues I mentioned above.
#Check WebClient instance and check if we need proxy credentials
$webclient = New-Object System.Net.WebClient
if (!($webclient.Proxy.IsBypassed("http://www.microsoft.com"))) {
$global:ProxyCred = Get-Credential
$webClient.Proxy.Credentials = $global:ProxyCred
}
#Function to get the return code from the URL
Function Test-Url {
param ($url)
$xHTTP = new-object -com msxml2.xmlhttp
$xHTTP.open("GET",$url,$false)
$xHTTP.send()
return $xHTTP.status
}
#Function to translate HelpInfoUri to download URL
Function Get-trueURL {
Param (
[String]$URL
)
$req = [System.Net.WebRequest]::Create($url)
$req.AllowAutoRedirect=$false
$req.Method="GET"
$req.AuthenticationLevel = "None"
if ($global:ProxyCred) {
$req.Proxy.Credentials = $global:ProxyCred
}
$resp=$req.GetResponse()
If ($resp.StatusCode -eq "Found" )
{
$tmpURI = $resp.GetResponseHeader("Location")
$resp.Close()
return $tmpURI
}
else
{
$tmpURI = $resp.responseURI
$resp.Close()
return $tmpURI
}
}
Function New-ModuleXML {
[CmdletBinding()]
param (
[String]$destinationPath
)
$moduleXml = @'
'@
if ($PSVersionTable.PSVersion.Major -eq 3) {
Write-Verbose "Processing modules on this computer"
$xml = [xml] $moduleXml
$modules = Get-Module -ListAvailable | ? { $_.HelpInfoUri -ne $null }
foreach ($module in $modules) {
if ($xml.Modules.ChildNodes.Count -gt 1) {
$newModule = $xml.Modules.Module[0].Clone()
} else {
$newModule = $xml.Modules.Module.Clone()
}
$newModule.Name = $module.Name.ToLower()
$newModule.HelpInfoUri = $module.HelpInfoUri
$newModule.Guid = $module.Guid.ToString()
$xml.Modules.AppendChild($newModule)
}
$xml.Save($DestinationPath)
Write-Verbose "Saved module information to $destinationPath"
}
}
#Get-WebFile from Poshcode.org
#http://poshcode.org/2461
Function Get-WebFile {
param(
[Parameter(Mandatory=$true)]
[String] $url,
[Parameter(Mandatory=$false)]
[String] $file,
[string]$destinationPath
)
begin {
$Global:downloadComplete = $false
$eventDataComplete = Register-ObjectEvent $Webclient DownloadFileCompleted `
-SourceIdentifier WebClient.DownloadFileComplete `
-Action {$Global:downloadComplete = $true}
$eventDataProgress = Register-ObjectEvent $Webclient DownloadProgressChanged `
-SourceIdentifier WebClient.DownloadProgressChanged `
-Action { $Global:DPCEventArgs = $EventArgs }
}
process {
Write-Progress -Activity 'Downloading $($file)' -Status $url
$Webclient.DownloadFileAsync($url, "$($destinationPath)\$($file)")
while (!($Global:downloadComplete)) {
$pc = $Global:DPCEventArgs.ProgressPercentage
if ($pc -ne $null) {
Write-Progress -Activity 'Downloading file' -Status $url -PercentComplete $pc
}
}
Write-Progress -Activity 'Downloading file' -Status $url -Complete
}
end {
Unregister-Event -SourceIdentifier WebClient.DownloadProgressChanged
Unregister-Event -SourceIdentifier WebClient.DownloadFileComplete
$Global:downloadComplete = $null
$Global:DPCEventArgs = $null
Remove-Variable eventDataComplete
Remove-Variable eventDataProgress
[GC]::Collect()
}
}
Function Get-HelpContent {
[CmdletBinding()]
param (
[String]$moduleName,
[String]$moduleUri,
[String]$moduleGuid,
[String]$UICulture,
[String]$destinationPath
)
$helpCab = "$($moduleName)_$($moduleGuid)_$($UICulture)_HelpContent.cab"
$helpXml = "$($moduleName)_$($moduleGuid)_HelpInfo.xml"
$helpCabUrl = "$($moduleUri)$($helpCab)"
$helpXmlUrl = "$($moduleUri)$($helpXml)"
$helpDescription = "Downloading help content for $($moduleName)"
if (((test-url $helpXmlUrl) -eq 200) -and ((test-url $helpCabUrl) -eq 200)) {
Write-Verbose "Processing $($moduleName) module"
Get-WebFile -url $helpXmlUrl -file $helpXml -DestinationPath $destinationPath
Get-WebFile -url $helpCabUrl -file $helpCab -DestinationPath $destinationPath
} else {
Write-Verbose "$($moduleName) module has no downloadble help yet!"
}
}
Function Save-Help {
[CmdletBinding()]
Param (
[String]$destinationPath,
[String]$UICulture = $Host.CurrentUICulture,
[String]$module = "*",
[String]$moduleXml
)
$moduleInfo = [xml](Get-Content $modulexml)
if ($module -eq "*") {
foreach ($mod in $moduleInfo.Modules.Module) {
$url = [uri] (Get-TrueUrl -url $mod.HelpInfoUri)
if ($url.AbsoluteUri.StartsWith('http://download.')) {
Get-HelpContent -moduleName $mod.Name -moduleUri $url -moduleGuid $mod.Guid -UICulture $UICulture -destinationPath $destinationPath
} else {
Write-Verbose "$($mod.Name) module has no downloadble help yet!"
}
}
} else {
$modName = $module.ToLower()
$xpath = "//Modules/Module[@Name=`"$modName`"]"
$selectedModule = (Select-Xml -Xml $moduleInfo -XPath $xpath).Node
$url = [uri] (Get-TrueUrl -url $selectedModule.HelpInfoUri)
if ($url.AbsoluteUri.StartsWith('http://download.')) {
Get-HelpContent -moduleName $selectedModule.Name -moduleUri $Url -moduleGuid $selectedModule.Guid -UICulture $UICulture -destinationPath $destinationPath
} else {
Write-Verbose "$($selectedModule.Name) module has no downloadble help yet!"
}
}
}
All you need is to save the above code as a .psm1 file and use the functions within this module to get the help content. This is how you need to use this module:
Generate a moduleXML for offline download purpose
First, we need to export the information we need to build the right URLs to an XML file. I am calling moduleXML. All you need to do is, load this module on a Windows 8 Server or client system or PowerShell 3.0 system to export the module name, module GUID, and HelpInfoUri to an XML file. This is done using:
New-ModuleXML -DestinationPath C:\Help\modules.xml
Once we have the XML file, we can use this module XML file anywhere to download the help content from any or all of the modules listed in the XML.
You can use the module.xml I generated on my Windows Server 8 beta system.
Here is how this module solves the issues I mentioned earlier.
Works behind a proxy
As soon as you load the module, it detects if you are behind a proxy and prompts you for the credentials required for proxy authentication.
Offline download of help for any module
To download the help content for the modules, this module uses the moduleXML created using the New-ModuleXML function. This is how you can use this:
This is it. This will download all available help content for the modules listed in the XML to a local folder. If you just want to download it for only one module, you can do so using:
Save-Help -Module Hyper-V -ModuleXML C:\Help\Modules.xml -DestinationPath c:\Help -Verbose
This is it. At the moment, not all modules seem to have the online help content. So, for some of the modules, you will just see a message that the help content is not available.
At the moment, this script is very raw. I have not included lot of error checking. Do let me know if you find any issues or have a suggestion.








Pingback: PowerShell Get-Script -Name Peter Kriegel | Management - PowerShell Get-Script -Name Peter Kriegel | Management
Pingback: Полезные примеры работы с PowerShell часть 2 « Kazun