Created
March 24, 2014 01:37
-
-
Save lidopaglia/9732726 to your computer and use it in GitHub Desktop.
Downloads DSC Resource Kits from TechNet Gallery
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<# | |
This Gist was created by ISEGist | |
03/23/2014 21:37:18 | |
#> | |
#requires -Version 4.0 | |
<# | |
.SYNOPSIS | |
Downloads DSC Resource Kits from TechNet Gallery | |
.DESCRIPTION | |
You probably would rather fork and clone the PowerShell.org community DSC repo on GitHub than bother with this. | |
But DSC is cool and I thought it might be neat to provide an example of how to download resources from the TechNet | |
Gallery (or any Uri really) and unpack them to a location of your choosing. | |
By default this script will hit an RSS feed for PowerShell DSC resources published on the TechNet ScriptCenter. It will | |
then download the zip file for the resource linked to by each article to the path specified. If no path is specified the | |
current user's $env:temp path is used. The downloaded zip file is then expanded into the path. The expanded folder is | |
searched for the first folder that contains a psd1 or psm1 file. If the root folder of the expanded resource | |
doesn't contain psm1 or psd1 files the script moves the first subfolder and its contents that contains them to the | |
download path. This helps keep the necessary module structure in place. Optionally you can specify to install the expanded | |
resources as well. If the install switch is specified the script will, by default, copy the exapnded resources to the user's | |
PowerShell Module path. You can also optionally specify an alternate install path. After the files are copied into | |
place (ak "installed") the script will prompt to remove anything that was downloaded and extacted in the path. | |
.NOTES | |
Author : Lido Paglia <https://twitter.com/nicemarmot> | |
Date : 03/16/2014 15:18:14 -04:00 | |
Tags : DSC, Download, Web, Module | |
Notes : Todo: | |
[X] Include basic comment based help | |
[ ] Add Support for SupportShouldProcess/-WhatIf | |
[ ] Add support for a PassThru parameter | |
[ ] include option to force cleanup without prompting the user. | |
.PARAMETER Path | |
The path to use as a working directory to save downloaded zip files and extracted resources. Defaults | |
to user's $env:temp path. | |
.PARAMETER Install | |
Optionally copies extracted resources into the InstallPath. By default InstallPath | |
points to the current users PowerShell Modules path. | |
.PARAMETER InstallPath | |
The path to use to copy extracted resources to. Defaults to the current user's Modules path. | |
.PARAMETER DownloadOnly | |
Only downloads the discovered resources as zip files to the specified path. | |
.EXAMPLE | |
DSCResourceDownloader.ps1 -Path C:\Temp | |
.EXAMPLE | |
DSCResourceDownloader.ps1 -Install | |
.EXAMPLE | |
DSCResourceDownloader.ps1 -Install -InstallPath D:\MyPath\For\Modules | |
.EXAMPLE | |
DSCResourceDownloader.ps1 -DownloadOnly | |
.LINK | |
http://blogs.msdn.com/b/powershell/archive/2014/02/07/need-more-dsc-resources-announcing-dsc-resource-kit-wave-2.aspx | |
#> | |
[cmdletbinding(DefaultParametersetName="None")] | |
Param( | |
[Parameter()] | |
[ValidateScript({if(Test-Path $_ -PathType Container){$true} | |
else{throw "$_ does not exist."}})] | |
[String] | |
$Path = $env:TEMP, | |
[Parameter(ParameterSetName="Install")] | |
[switch] | |
$Install, | |
[Parameter(ParameterSetName="Install")] | |
[ValidateScript({if(Test-Path $_ -PathType Container){$true} | |
else{throw "`'$_`' does not exist."}})] | |
[string] | |
$InstallPath = "$env:USERPROFILE\Documents\WindowsPowerShell\Modules", | |
[Parameter(ParameterSetName="Download")] | |
[switch] | |
$DownloadOnly | |
) | |
function Expand-ZipFile | |
{ | |
<# | |
.SYNOPSIS | |
Uses CopyHere method of Shell.Application to extract the contents of a Zip archive to the specified path. | |
.NOTES | |
Author : Lido Paglia <[email protected]> | |
Date : 03/22/2014 14:54:26 -04:00 | |
Tags : Files, Zip, Archive, Expand | |
.PARAMETER File | |
The full path to the .zip file archive to extract. | |
.PARAMETER Destination | |
The full path to the folder where the .zip archive should be extracted. | |
.PARAMETER UILevel | |
The Level of interaction to display. This can be Silent or Progress. If not specified the default value | |
is Progress. | |
.PARAMETER PassThru | |
If specified will pass the resulting COM object of the file being operated on to the pipeline. | |
.INPUTS | |
None. You cannot pipe objects to Expand-ZipFile. | |
.OUTPUTS | |
None. | |
.EXAMPLE | |
Expand-ZipFile | |
.EXAMPLE | |
Expand-ZipFile | |
.LINK | |
http://msdn.microsoft.com/en-us/library/windows/desktop/bb787866(v=vs.85).aspx | |
#> | |
Param( | |
[Parameter()] | |
[ValidateScript({Test-Path $_ -PathType Leaf})] | |
[string] | |
$File, | |
[Parameter()] | |
[ValidateScript({Test-Path $_ -PathType Container})] | |
[string] | |
$Destination, | |
[Parameter()] | |
[ValidateSet("Silent","Progress")] | |
[string] | |
$UILevel = "Progress", | |
[Parameter()] | |
[switch] | |
$PassThru | |
) | |
$FOF_SILENT = 0x4 | |
$FOF_RENAMEONCOLLISION = 0x8 | |
$FOF_NOCONFIRMATION = 0x10 | |
$FOF_ALLOWUNDO = 0x40 | |
$FOF_FILESONLY = 0x80 | |
$FOF_SIMPLEPROGRESS = 0x100 | |
$FOF_NOCONFIRMMKDIR = 0x200 | |
$FOF_NOERRORUI = 0x400 | |
$FOF_NOCOPYSECURITYATTRIBS = 0x800 | |
$FOF_NORECURSION = 0x1000 | |
$FOF_NO_CONNECTED_ELEMENTS = 0x2000 | |
switch($UILevel) | |
{ | |
Silent {$Flags = $FOF_SILENT + $FOF_NOCONFIRMATION + $FOF_NOERRORUI} | |
Progress {$Flags = $FOF_NOCONFIRMATION + $FOF_SIMPLEPROGRESS} | |
} | |
$shell = New-Object -ComObject Shell.Application | |
$zip = $shell.NameSpace($file) | |
foreach($item in $zip.items()) | |
{ | |
$shell.Namespace($destination).copyhere($item,$Flags) | |
if($passthru) | |
{ | |
$item | |
} | |
} | |
} | |
function Read-PromptForChoice | |
{ | |
<# | |
.SYNOPSIS | |
Prompts the user to select a choice. | |
.DESCRIPTION | |
Given a title, message, and ordered hashtable of options Read-PromptForChoice will call the | |
PromptForChoice method of the PSHostUserInterface class. This provides a uniform and friendly | |
experience when you have a need to prompt the user to interactively make a decision in response | |
to actions carried out in script. | |
.NOTES | |
Author : Lido Paglia <[email protected]> | |
Date : 01/25/2014 21:20:49 | |
.PARAMETER Title | |
The text to display preceeding the choice. In the ISE this title is displayed in the window title. | |
.PARAMETER Message | |
Some text that describes the choice. This is secondary to the title. In the ISE this text appears | |
as the message box text. | |
.PARAMETER Options | |
A collection of options specified as an ordered hashtable. The object supplied to the Options parameter | |
must be an Ordered hash table. The 'Name' value is the text displayed on the menu button in the ISE or the | |
full option text in the Console Host. The 'Value' corresponds to the help message text for each supplied option. | |
This help text can be accessed in the shell by typing [?] for help or in the ISE by hovering over the button choice. | |
.INPUTS | |
None. You cannot pipe objects to Read-PromptForChoice. | |
.OUTPUTS | |
System.Int32. Read-PromptForChoice returns an integer value corresponding to the order of menu options provided. | |
.EXAMPLE | |
Read-PromptForChoice -Title "Hi, I'm a Title" -Message "This is the message." -Options ([ordered]@{Yes='You know you want to.';No='Just say no.'}) | |
.EXAMPLE | |
$PromptParams = @{ | |
Title = "WARNING: Uneven Names" | |
Message = "The list of names are uneven. Want to pick a name to use more than once?" | |
Options = ([ordered]@{ | |
Yes = "Select someone to use more than once." | |
No = "Quit." | |
}) | |
} | |
$result = Read-PromptForChoice @PromptParams | |
switch ($result) | |
{ | |
0 {$Dupe = $Names | Out-GridView -Outputmode Single} | |
1 {return} | |
} | |
.EXAMPLE | |
[scriptblock]$sb = {Get-Process} | |
$PromptParams = @{ | |
Title = "List running Services or Processes" | |
Message = "Want to run Get-Service or Get-Process?" | |
Options = ([ordered]@{ | |
Service = "execute Get-Service" | |
Process = "execute Get-Process" | |
Cancel = "Quit" | |
}) | |
} | |
$result = Read-PromptForChoice @PromptParams | |
switch ($result) | |
{ | |
0 {Get-Service} | |
1 {Invoke-Command $sb} | |
2 {return} | |
} | |
.LINK | |
http://msdn.microsoft.com/en-us/library/system.management.automation.host.pshostuserinterface.promptforchoice(v=vs.85).aspx | |
#> | |
Param( | |
[string]$Title, | |
[string]$Message, | |
[Collections.Specialized.OrderedDictionary]$Options | |
) | |
$varOptions = @() | |
$Options.GetEnumerator() | foreach { | |
$varParams = @{ | |
Name = $_.Name | |
Value = (New-Object System.Management.Automation.Host.ChoiceDescription "&$($_.Name)", "$($_.Value)") | |
} | |
$varOptions += @(New-Variable @varParams -PassThru -Force) | |
} | |
$Host.UI.PromptForChoice($title, $message, ($varOptions.GetEnumerator().Value), 0) | |
} | |
<# ValidateScript block bypasses this | |
if(-Not(Test-Path $InstallPath)) | |
{ | |
# optionally create InstallPath | |
[scriptblock]$sb = { | |
try | |
{ | |
mkdir $InstallPath -Force -ErrorAction Stop | Out-Null | |
} | |
catch | |
{ | |
Write-Error $_ | |
return | |
} | |
} | |
$PromptParams = @{ | |
Title = "Create Module Path" | |
Message = "Create folder $InstallPath ?" | |
Options = ([ordered]@{ | |
Yes = "Create Path" | |
No = "Quit" | |
}) | |
} | |
switch (Read-PromptForChoice @PromptParams) | |
{ | |
0 {Invoke-Command $sb} | |
1 {break} | |
} | |
} | |
#> | |
$count = 0 | |
$TempFiles = @() | |
$baseUri = 'http://gallery.technet.microsoft.com' | |
$rssStub = '/site/feeds/searchRss?f%5B0%5D.Type=SearchText&f%5B0%5D.Value=dsc%20resource%20kit&sortBy=Relevance' | |
$Uri = $baseUri + $rssStub | |
# get the list of available dsc resource entries | |
$Resources = Invoke-RestMethod $Uri | Where-Object {$_.Title -match "(?:dsc|desired state configuration)?Resource Kit"} | |
#$Resources=$Resources[0] | |
foreach($resource in $Resources) | |
{ | |
Write-Verbose "Downloading $($resource.title)" | |
$count++ | |
$progressParam =@{ | |
Activity = "Downloading DSC Resources" | |
Status = "Downloading: $($resource.title)" | |
PercentComplete = ($count / $resources.count*100) | |
} | |
Write-Progress @progressParam | |
try | |
{ | |
$dlStub = (Invoke-WebRequest $resource.link -ErrorAction Stop).links | | |
Where-Object {$_.href -match ".zip"} | | |
Sort-Object href -Unique -Descending | | |
Select-Object -ExpandProperty href -First 1 | |
} | |
catch | |
{ | |
Write-Error "Could not parse download URI from $($resource.link) - $($_.exception.message)" | |
continue | |
} | |
$dlUri = $baseUri + $dlStub | |
$targetFilePath = Join-Path $Path -ChildPath (Split-Path -Path $dlUri -Leaf) | |
$filename = Split-Path -Path $dlUri -Leaf | |
try | |
{ | |
# download the zip file | |
Invoke-WebRequest -Uri $dlUri -OutFile $targetFilePath -ErrorAction Stop | |
Write-Verbose "Downloaded $filename to $Path" | |
} | |
catch | |
{ | |
Write-Error $_.Exception.Message | |
continue | |
} | |
# verify the file downloaded and exists on disk | |
if(-Not(Test-Path $targetFilePath)) | |
{ | |
Write-Error "$targetFilePath was not written to disk! - $($_.exception.message)" | |
continue | |
} | |
if(-Not$DownloadOnly) | |
{ | |
# expand the downloaded archive | |
$destinationPath = Split-Path $targetFilePath -Parent | |
$Expanded = Expand-ZIPFile -File $targetFilePath -Destination $destinationPath -UILevel Silent -PassThru | |
$ExpandedSource = Join-Path $destinationPath -ChildPath $Expanded.Name | |
# remove the downloaded .zip file | |
try | |
{ | |
Split-Path $Expanded.path -Parent | Remove-Item -Force -ErrorAction Stop | |
Write-Verbose "Removed $(Split-Path $Expanded.path)" | |
} | |
catch | |
{ | |
Write-Error $_ | |
continue | |
} | |
# get the parent of the first folder containing a psd1 or psm1 | |
$ModuleSourcePath = (Get-ChildItem $ExpandedSource\* -Include "*.psd1","*.psm1" -Recurse | | |
Sort-Object fullname -Descending | | |
Select-Object -First 1).DirectoryName | |
# if the root folder of the expanded source doesn't contain psm1 or psd1 files | |
# move the first subfolder that contains them to the download path | |
if(-NOT((dir $ExpandedSource).name | Where-Object {$_ -match ".psd1$|.psm1$"})) | |
{ | |
Write-Verbose "$ExpandedSource does not contain module manifest (.psd1) or module (.psm1) file." | |
try | |
{ | |
$Dest = Join-Path $destinationPath -ChildPath (Split-Path $ModuleSourcePath -Leaf) | |
if(test-path $dest) | |
{ | |
Remove-Item $dest -Recurse -Force -ErrorAction Stop | |
Write-Verbose "Removed Existing Path $dest" | |
} | |
Move-Item -Path $ModuleSourcePath -Destination $destinationPath -Force -ErrorAction Stop | |
Write-Verbose "Moved Sub-Folder: $(Split-Path $ModuleSourcePath -Leaf) to $DestinationPath" | |
Remove-Item $ExpandedSource -Force -ErrorAction Stop | |
Write-Verbose "Removed Empty Parent Folder: $ExpandedSource" | |
$ModuleSourcePath = $dest | |
} | |
catch | |
{ | |
Write-Error $_.Exception.Message | |
} | |
} | |
if($Install) | |
{ | |
# optionally install the module by copying to program files path | |
Copy-Item -Path $ModuleSourcePath -Destination $InstallPath -Recurse -Force | |
Write-Verbose "Copied $($ModuleSourcePath.Name) to $InstallPath" | |
$TempFiles += $ModuleSourcePath | |
} | |
} | |
} | |
if($TempFiles) | |
{ | |
# optionally clean up downloaded temp files | |
[scriptblock]$sb = { | |
# delete files from temp download location as they are now installed | |
$TempFiles | Remove-Item -Recurse -Force | |
} | |
$PromptParams = @{ | |
Title = "Cleanup" | |
Message = "Remove downloaded files from $Path ?" | |
Options = ([ordered]@{ | |
Yes = "delete downloaded files" | |
No = "ignore downloaded files" | |
}) | |
} | |
switch (Read-PromptForChoice @PromptParams) | |
{ | |
0 {Invoke-Command $sb} | |
1 {continue} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment