Skip to content

Instantly share code, notes, and snippets.

@lidopaglia
Created March 24, 2014 01:37
Show Gist options
  • Save lidopaglia/9732726 to your computer and use it in GitHub Desktop.
Save lidopaglia/9732726 to your computer and use it in GitHub Desktop.
Downloads DSC Resource Kits from TechNet Gallery
<#
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 <lido@paglia.org>
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 <lido@paglia.org>
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