Restore-NuGet.ps1

<#PSScriptInfo
.VERSION
    0.1
.GUID
    9925e87b-552a-45c0-89e9-73673dd588af
.AUTHOR
    Slava Sharashkin
.DESCRIPTION
    The script restores the specific version of NuGet package and dependences. Currently only strongly typed version are supported.
.COMPANYNAME
 
.COPYRIGHT
 
.TAGS
    NuGet
.LICENSEURI
    https://raw.githubusercontent.com/slavashar/Restore-NuGet/master/LICENSE
.PROJECTURI
    https://github.com/slavashar/Restore-NuGet
.ICONURI
 
.EXTERNALMODULEDEPENDENCIES
 
.REQUIREDSCRIPTS
 
.EXTERNALSCRIPTDEPENDENCIES
 
.RELEASENOTES
#>

function Restore-NuGet {
    <#
    .SYNOPSIS
        Retores NuGet package
    .DESCRIPTION
        The script restores the specific version of NuGet package and dependences. Currently only strongly typed version are supported.
    .PARAMETER PackageId
        A specific package id to restore
    .PARAMETER Version
        A specific version of the package to restore
    .PARAMETER Source
        Comma separeted list of sources
    .PARAMETER OutputDirectory
        Destination location of the restored packages
    .PARAMETER NoCache
        Indicates that no cache should be used
    #>


    Param(
        [Parameter(Mandatory=$True)]
        [ValidateNotNull()]
        [String]$PackageId,

        [Parameter(Mandatory=$True)]
        [ValidateNotNull()]
        [String]$Version,

        [String]$Source = "https://www.nuget.org/api/v2/",

        [String]$OutputDirectory = (Get-Location),
    
        [switch]$NoCache)

    Add-Type -Assembly "WindowsBase"

    if (!(Test-Path $OutputDirectory)) {
        $outputDir = New-Item $OutputDirectory -ItemType directory   
    }
    else {
        $outputDir = Get-Item $OutputDirectory
    }

    $services = New-Object System.Collections.Generic.HashSet[object]

    $Source.Split(';') | Foreach {
        if ($_.StartsWith("http")) {
            [void]$services.Add((New-Object Uri -ArgumentList $_))
        }
        else {
            [void]$services.Add((Get-Item $_))
        }
    }

    function ExtractPackage($stream, $packageId, $version) {
        $directory = New-Item (Join-Path $outputDir "$packageId.$version") -ItemType directory
        
        $nupkgStartPosition = $stream.Position;
        $archive = [System.IO.Packaging.Package]::Open($stream)

        foreach ($part in $archive.GetParts()) {
        
            if ($part.ContentType -eq "application/vnd.openxmlformats-package.relationships+xml") {
                continue
            }
        
            if ($part.ContentType -eq "application/vnd.openxmlformats-package.core-properties+xml") {
                continue
            }

            if ($part.Uri.OriginalString -eq "/$packageId.nuspec") {
                $reader = New-Object System.IO.StreamReader -ArgumentList $part.GetStream()
                $spec = [xml] $reader.ReadToEnd()
                continue
            }

            $partPath = Join-Path $directory ([Uri]::UnescapeDataString($part.Uri.OriginalString))

            $file = New-Item $partPath -ItemType file -Force
            $writer = $file.OpenWrite()
            [void]$part.GetStream().CopyTo($writer)
            [void]$writer.Close()
        }

        [void]$archive.Close()

        [void]$stream.Seek($nupkgStartPosition, [System.IO.SeekOrigin]::Begin);

        $partPath = Join-Path $directory "$packageId.$version.nupkg"

        $file = New-Item $partPath -ItemType file -Force
        $writer = $file.OpenWrite()
        [void]$stream.CopyTo($writer)
        [void]$writer.Close()

        return $spec
    }

    function GetSpec($nupkg) {
        $archive = [System.IO.Packaging.Package]::Open($nupkg)
        $packageId = $archive.PackageProperties.Identifier

        $specPart = $archive.GetPart((New-Object Uri -ArgumentList @( "/$packageId.nuspec", [UriKind]::Relative )));

        $reader = New-Object System.IO.StreamReader -ArgumentList $specPart.GetStream()
        $spec = [xml] $reader.ReadToEnd()

        [void]$archive.Close()

        return $spec
    }

    function RestorePackage($packageId, $version) {
        $directory = Join-Path $outputDir "$packageId.$version"
        $cachePath = Join-Path $env:LOCALAPPDATA "NuGet\Cache"
        $cachePackagePath = Join-Path $cachePath "$packageId.$version.nupkg"

        if (Test-Path $directory) {
            $spec = GetSpec((Join-Path $directory "$packageId.$version.nupkg"))
        
            Write-Host $packageId $version already installed
        }
        elseif ((-not $NoCache) -and (Test-Path $cachePackagePath)) {
                $stream =  [System.IO.File]::OpenRead($cachePackagePath)

                $spec = ExtractPackage -stream $stream -packageId $packageId -version $version

                $stream.Close()
            
                Write-Host Successfully installed $packageId $version from cache
        }
        else {
            $packageLocation = $null

            foreach ($service in $services) {
            
                if ($service -is [Uri]) {
                    try {
                        $packageResponce = Invoke-RestMethod (New-Object Uri -ArgumentList @( $service, "Packages(Id='$packageId',Version='$version')" ))
                    }
                    catch {
                        continue
                    }

                    $packageLocation = $packageResponce.entry.content.src
                    break
                }
                else {
                    $tmp = Join-Path $service "$packageId.$version.nupkg"
                    if (Test-Path $tmp) {
                        $packageLocation = $tmp
                        break
                    }
                }
            }

            if ($packageLocation -eq $null) {
                throw "Unable to find version $version of package $packageId"
            }

            if ($packageLocation -is [string] -and $packageLocation.StartsWith("http")) {
                $packageContent = (New-Object Net.WebClient).DownloadData($packageLocation)
            }
            elseif ($packageLocation -is [string]) {
                $packageContent = [System.IO.File]::ReadAllBytes($packageLocation)
            }
            else {
                throw "Unable to retrieve the package"
            }

            if ((-not $NoCache)) {
                if (!(Test-Path $cachePath)) {
                    New-Item $cachePath -ItemType directory | Out-Null
                }

                [System.IO.File]::WriteAllBytes($cachePackagePath, $packageContent)
            }

            try {
                $stream = New-Object System.IO.MemoryStream -ArgumentList @(, $packageContent)

                $spec = ExtractPackage -stream $stream -packageId $packageId -version $version
            }
            finally {
                $stream.Close()
            }
            
            Write-Host Successfully installed $packageId $version from source
        }

        return $spec
    }

    $installedPackages = @{}
    $requiredPackages = @{}

    $requiredPackages.Add($PackageId, $Version)

    while ($requiredPackages.Count -gt 0) {
        $packageId = $requiredPackages.Keys | select -First 1
        $version = $requiredPackages[$packageId]

        Write-Host Restoring $packageId $version

        $spec = RestorePackage $packageId $version

        $requiredPackages.Remove($packageId)
        $installedPackages.Add($packageId, $version)

        foreach ($dependency in ($spec.package.metadata.dependencies | foreach { $_.dependency })) {
            $dependencyPackageId = $dependency.id
            $dependencyVersion = $dependency.version

            if ($installedPackages.ContainsKey($dependencyPackageId)) {
                #TODO check the version
            }
            elseif ($requiredPackages.ContainsKey($dependencyPackageId)){
                #TODO check the version
            }
            else {
                $requiredPackages.Add($dependencyPackageId, $dependencyVersion)
            }
        }
    }

    $result = @()

    foreach ($packageId in $installedPackages.Keys) {
        $version = $installedPackages[$packageId]

        $pkg = New-Object System.Object
        $pkg | Add-Member -MemberType NoteProperty -Name "Id" -Value $packageId
        $pkg | Add-Member -MemberType NoteProperty -Name "Version" -Value $version
        $pkg | Add-Member -MemberType NoteProperty -Name "Location" -Value (Join-Path $outputDir "$packageId.$version")

        $result += $pkg
    }

    $result
}