SourceTypes/PSGallery/PSGallery.psm1

Microsoft.PowerShell.Core\Set-StrictMode -Version Latest

$script:PSGetSourceType = "PSGallery"
$script:NuGetBinariesPath="$env:LOCALAPPDATA\Microsoft\Windows\PowerShell\PowerShellGet"
$script:NuGetClient = "$script:NuGetBinariesPath\NuGet.exe"
$script:NuGetAssembly = $null

#These two variables allow us to change the Source and Publish URIs for PSGallery
$PSGallerySourceUri  = "https://dtlgalleryint.cloudapp.net/api/v2/"
$PSGalleryPublishUri = $PSGallerySourceUri+"package/"

Microsoft.PowerShell.Utility\Import-LocalizedData  LocalizedData -filename PSGet.Resource.psd1 -BaseDirectory "$PSScriptRoot\..\.."

# Check if current user is running with elevated privileges
function Test-RunningAsElevated
{
    [CmdletBinding()]
    [OutputType([bool])]
    Param()

    $wid=[System.Security.Principal.WindowsIdentity]::GetCurrent()
    $prp=new-object System.Security.Principal.WindowsPrincipal($wid)
    $adm=[System.Security.Principal.WindowsBuiltInRole]::Administrator
    return $prp.IsInRole($adm)
}

# Utility to throw an errorrecord
function ThrowError
{    
    param
    (        
        [parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [System.String]        
        $ExceptionName,

        [parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [System.String]
        $ExceptionMessage,
        
        [System.Object]
        $ExceptionObject,
        
        [parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [System.String]
        $ErrorId,

        [parameter(Mandatory = $true)]
        [ValidateNotNull()]
        [System.Management.Automation.ErrorCategory]
        $ErrorCategory
    )
        
    $exception = New-Object $ExceptionName $ExceptionMessage;
    $errorRecord = New-Object System.Management.Automation.ErrorRecord $exception, $ErrorId, $ErrorCategory, $ExceptionObject
    throw $errorRecord
}

function Install-NuGetClientBinaries
{
    $NuGetClientSource = "https://nuget.org/nuget.exe"

    $ShouldContinueQueryMessage = $LocalizedData.InstallNuGetBinariesShouldContinueQuery -f @($script:NuGetBinariesPath,$script:NuGetBinariesPath)

    if($PSCmdlet.ShouldContinue($ShouldContinueQueryMessage, $LocalizedData.InstallNuGetBinariesShouldContinueCaption))
    {
        Write-Verbose -Message $LocalizedData.DownloadingNugetBinaries

        $null = Microsoft.PowerShell.Management\New-Item -Path $script:NuGetBinariesPath -ItemType Directory -Force -ErrorAction SilentlyContinue -WarningAction SilentlyContinue -Confirm:$false -WhatIf:$false

        if(-not (Microsoft.PowerShell.Management\Test-Path $script:NuGetClient))
        {
            # Download the NuGet.exe from https://nuget.org/NuGet.exe
            $null = Microsoft.PowerShell.Utility\Invoke-WebRequest -Uri $NuGetClientSource -OutFile $script:NuGetClient
        }
    }

    if(-not (Microsoft.PowerShell.Management\Test-Path $script:NuGetClient))
    {
        $message = $LocalizedData.CouldNotInstallNuGetBinaries -f @($script:NuGetBinariesPath)
        ThrowError -ExceptionName "System.InvalidOperationException" `
                    -ExceptionMessage $message `
                    -ErrorId "CouldNotInstallNuGetBinaries" `
                    -ErrorCategory InvalidOperation
    }
}

function New-PSGetItemInfo
{
    param(
        [Parameter(Mandatory=$true)]
        [ValidateNotNull()]
        $packageInfo
    )

    $PSGetItemInfo = Microsoft.PowerShell.Utility\New-Object PSCustomObject -Property ([ordered]@{
            Name = $packageInfo.Id
            Version = [Version]$packageInfo.Version.ToString()
                    
            Description = $packageInfo.Description
            Author = $packageInfo.Authors
            CompanyName = $packageInfo.Owners
            Copyright = $packageInfo.CopyRight

            LicenseUri = $packageInfo.LicenseUrl
            ProjectUri = $packageInfo.ProjectUrl
            IconUri = $packageInfo.IconUrl
            Tag = $packageInfo.Tags -split ' '
            ReleaseNotes = $packageInfo.ReleaseNotes

            # Few properties are not avialable for packages hosted on local directory based NuGet repository
            DateUpdated = if(Get-Member -InputObject $packageInfo -Name LastUpdated) { $packageInfo.LastUpdated.LocalDateTime } else { $packageInfo.Published.LocalDateTime }

            RequiredModules = if(Get-Member -InputObject $packageInfo -Name Dependencies) { $packageInfo.Dependencies } else { $packageInfo.DependencySets }
            
            DownloadUri = if(Get-Member -InputObject $packageInfo -Name DownloadUrl) { $packageInfo.DownloadUrl } else { $null }
            
            Hash = if(Get-Member -InputObject $packageInfo -Name PackageHash) { $packageInfo.PackageHash } else { $null }
            
            HashAlgorithm = if(Get-Member -InputObject $packageInfo -Name PackageHashAlgorithm) { $packageInfo.PackageHashAlgorithm } else { $null }

            SourceUri = $PSGallerySourceUri
            SourceType = $script:PSGetSourceType
        })

    $PSGetItemInfo.PSTypeNames.Insert(0, "Microsoft.PowerShell.Commands.PSGetItemInfo")
    $PSGetItemInfo
}

function Get-PSGetItemInfo
{
    [CmdletBinding()]
    [OutputType("PSCustomObject")]
    param(
        [Parameter(Mandatory=$true)]
        [ValidateNotNull()]
        $Name,

        [Parameter(Mandatory=$true)]
        [ValidateNotNull()]
        $RepoObject,

        [Parameter()]
        [ValidateNotNull()]
        [Version]
        $Version
    )

    if($Version)
    {
        $packageInfo = [NuGet.PackageRepositoryExtensions]::FindPackage($RepoObject, $Name, $Version)
    }
    else
    {
        $packageInfo = [NuGet.PackageRepositoryExtensions]::FindPackage($RepoObject, $Name)
    }

    if($packageInfo)
    {
        New-PSGetItemInfo -packageInfo $packageInfo
    }
}

function Test-WildcardPattern
{
    [CmdletBinding()]
    [OutputType([bool])]
    param(
        [Parameter(Mandatory=$true)]
        [ValidateNotNull()]
        $Name
    )

    return [System.Management.Automation.WildcardPattern]::ContainsWildcardCharacters($Name)    
}

function Find-PSGetExtModule
{
    [CmdletBinding(DefaultParameterSetName='ModuleName',PositionalBinding=$false)]
    Param
    (
        [Parameter(ValueFromPipelineByPropertyName=$true,
                   ParameterSetName='ModuleName')]
        [string[]]
        $Name,

        [Parameter(ValueFromPipelineByPropertyName=$true, 
                   ParameterSetName='FullyQualifiedName')]
        [ValidateNotNullOrEmpty()]
        [HashTable[]]
        $FullyQualifiedName
    )

    Begin
    {
        if(-not (Microsoft.PowerShell.Management\Test-Path $script:NuGetClient))
        {
            Install-NuGetClientBinaries
        }

        if(-not $script:NuGetAssembly)
        {
            $tempNuGetExePath = "$env:TEMP\NuGet_ForPSGet.exe"
            if(-not (Microsoft.PowerShell.Management\Test-Path -Path $tempNuGetExePath))
            {
                Microsoft.PowerShell.Management\Copy-Item -Path $script:NuGetClient -Destination $tempNuGetExePath -Force `
                                                          -Confirm:$false -WhatIf:$false `
                                                          -ErrorAction SilentlyContinue -WarningAction SilentlyContinue
            }
            $script:NuGetAssembly = [System.Reflection.Assembly]::LoadFile($tempNuGetExePath)
        }

        # Create repository object for getting module metadata from local repository or from gallery service
        if(Microsoft.PowerShell.Management\Test-Path $PSGallerySourceUri)
        {
            $NDSPackageRepo = New-Object NuGet.LocalPackageRepository $PSGallerySourceUri
        }
        else
        {
            $psGalleryUri = $null
            try
            {
                $request = [System.Net.WebRequest]::Create($PSGallerySourceUri)
                $request.Method = 'GET'
                $response = $request.GetResponse()               
                $psGalleryUri = $response.ResponseUri
                $response.Close()
            }
            catch
            {
                # Ignoring exceptions
            }

            if($psGalleryUri -eq $null)
            {
                $psGalleryUri = New-Object System.Uri $PSGallerySourceUri
            }

            $NDSPackageRepo = New-Object Nuget.DataServicePackageRepository $psGalleryUri
        }

        # Wildcard pattern matching configuration.
        $wildcardOptions = [System.Management.Automation.WildcardOptions]::CultureInvariant -bor `
                           [System.Management.Automation.WildcardOptions]::IgnoreCase
    }

    Process
    {
        switch($PSCmdlet.ParameterSetName) 
        {
            "ModuleName" {
                if(-not $Name)
                {
                    Write-Verbose -Message $LocalizedData.FindingAvailableModules
                    $result = & $script:NuGetClient list -source $PSGallerySourceUri -NonInteractive

                    if($result -eq "No packages found.")
                    {
                        Write-Verbose -Message $LocalizedData.NoModulesFound
                        return
                    }

                    Write-Verbose -Message ($LocalizedData.FoundModules -f $result.count)

                    foreach($line in $result)
                    {
                        # each line in results has two parts: name version
                        $metadata = $line.Split()

                        Get-PSGetItemInfo -Name $metadata[0] -RepoObject $NDSPackageRepo
                    }
                }
                else
                {
                    foreach($artifactName in $Name)
                    {
                        if(Test-WildcardPattern -Name $artifactName)
                        {
                            # NuGet does not support PowerShell/POSIX style wildcards and supports only '*' in names
                            # Replace the range from '[' - to ']' with * and ? with * then wildcard pattern is applied on the results from NuGet.exe
                            $tempName = $artifactName
                            $squareBracketPattern = [System.Text.RegularExpressions.Regex]::Escape("[") + "(.*?)]"
                            $matches = [System.Text.RegularExpressions.Regex]::Matches($tempName, $squareBracketPattern)
                            $matches | Microsoft.PowerShell.Core\ForEach-Object {$tempName = $tempName.Replace($_, "*")}
                            $tempName = $tempName.Replace("?", "*")

                            $searchTerm = "$tempName"
                        }
                        else
                        {
                            $searchTerm = "id:$artifactName"
                        }

                        $moduleFound = $false

                        $result = & $script:NuGetClient list $searchTerm -source $PSGallerySourceUri -NonInteractive

                        if($result -ne "No packages found.")
                        {
                            $wildcardPattern = New-Object System.Management.Automation.WildcardPattern $artifactName,$wildcardOptions

                            foreach($line in $result)
                            {
                                $metadata = $line.Split()
                            
                                if($wildcardPattern.IsMatch($metadata[0]))
                                {
                                    Get-PSGetItemInfo -Name $metadata[0] -RepoObject $NDSPackageRepo

                                    Write-Verbose -Message ($LocalizedData.FoundModuleWithVersion -f ($metadata[0], $metadata[1]))

                                    $moduleFound = $true
                                }
                            }
                        }

                        if(-not $moduleFound)
                        {
                            $message = $LocalizedData.ModuleNotFound -f ($artifactName) 
                            
                            if(-not (Test-WildcardPattern -Name $artifactName))
                            {
                                Write-Error -Message $message -ErrorId "ModuleNotFound" -Category InvalidOperation
                            }

                            Write-Verbose -Message $message
                        }
                    }
                }
            
                break
            }

            "FullyQualifiedName" {
                foreach($fqName in $FullyQualifiedName)
                {
                    if($fqName.ContainsKey("RequiredVersion"))
                    {
                        $searchTerm = "$($fqName.ModuleName)"
                    }
                    else
                    {
                        $searchTerm = "id:$($fqName.ModuleName)"
                    }
                    
                    $RequiredVersion = $null
                    $ModuleVersion = $null

                    if($fqName.ContainsKey("RequiredVersion"))
                    {
                        $RequiredVersion = $fqName.RequiredVersion
                        $result = & $script:NuGetClient list $searchTerm -AllVersions -source $PSGallerySourceUri -NonInteractive
                    }
                    else
                    {
                        $ModuleVersion = $fqName.ModuleVersion
                        $result = & $script:NuGetClient list $searchTerm -source $PSGallerySourceUri -NonInteractive
                    }

                    $moduleFound = $false

                    if($result -ne "No packages found.")
                    {
                        foreach($line in $result)
                        {
                            $metadata = $line.Split()

                            if($metadata[0] -like $fqName.ModuleName -and 
                               (($RequiredVersion -and ([Version]$metadata[1] -eq $RequiredVersion)) -or
                                ($ModuleVersion   -and ([Version]$metadata[1] -ge $ModuleVersion)))
                              )
                            {
                                Get-PSGetItemInfo -Name $metadata[0] -RepoObject $NDSPackageRepo -Version $([Version]$metadata[1])

                                Write-Verbose -Message ($LocalizedData.FoundModuleWithVersion -f ($metadata[0], $metadata[1]))
                            
                                $moduleFound = $true
                            }
                        }
                    }

                    if(-not $moduleFound)
                    {
                        if($RequiredVersion)
                        {
                            $message = $LocalizedData.ModuleNotFoundWithRequiredVersion -f ($fqName.ModuleName, $RequiredVersion)
                            Write-Error -Message $message -ErrorId "ModuleNotFoundWithRequiredVersion" -Category InvalidOperation
                        }
                        else
                        {
                            $message = $LocalizedData.ModuleNotFoundWithMinimumVersion -f ($fqName.ModuleName, $ModuleVersion)
                            Write-Error -Message $message -ErrorId "ModuleNotFoundWithMinimumVersion" -Category InvalidOperation
                        }

                        continue
                    }                    
                }
            
                break
            }
        }
    }
}

function Get-PSGetExtModule
{
    [CmdletBinding(PositionalBinding=$false)]
    [OutputType([Bool])]
    Param
    (
        [Parameter(Mandatory=$true)]
        [ValidateNotNull()]
        [PSCustomObject]
        $PSGetItemInfo,

        [Parameter(Mandatory=$true)]
        [ValidateNotNull()]
        [string]
        $OutputDirectory
    )

    if(-not (Microsoft.PowerShell.Management\Test-Path $script:NuGetClient))
    {
        Install-NuGetClientBinaries        
    }

    $output = & $script:NuGetClient install $PSGetItemInfo.Name -Version $PSGetItemInfo.Version -source $PSGallerySourceUri -OutputDirectory $OutputDirectory -ExcludeVersion -NonInteractive
    if($LASTEXITCODE)
    {
        return $false
    }

    $moduleBase = Microsoft.PowerShell.Management\Join-Path $OutputDirectory $PSGetItemInfo.Name
    if(Microsoft.PowerShell.Management\Test-Path $moduleBase)
    {
        Microsoft.PowerShell.Management\Remove-Item -Path "$moduleBase\*.nupkg" -Force -ErrorAction SilentlyContinue -WarningAction SilentlyContinue -Confirm:$false -WhatIf:$false
    }

    return $true
}

function Get-EscapedString
{
    [CmdletBinding()]
    [OutputType([String])]
    Param
    (
        [Parameter()]
        [string]
        $ElementValue
    )

    return [System.Security.SecurityElement]::Escape($ElementValue)
}

function Publish-PSGetExtModule
{
    [CmdletBinding(PositionalBinding=$false)]
    Param
    (
        [Parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [PSModuleInfo]
        $PSModuleInfo,

        [Parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [string]
        $NugetApiKey,

        [Parameter()]
        [string]
        $ReleaseNotes,

        [Parameter()]
        [string[]]
        $Tag,
        
        [Parameter()]
        [string]
        $LicenseUri,

        [Parameter()]
        [string]
        $IconUri,
        
        [Parameter()]
        [string]
        $ProjectUri
    )

    if(-not (Microsoft.PowerShell.Management\Test-Path $script:NuGetClient))
    {
        Install-NuGetClientBinaries
    }

    # Populate the nuspec elements
    $nuspec = @"
<?xml version="1.0"?>
<package >
    <metadata>
        <id>$(Get-EscapedString -ElementValue $PSModuleInfo.Name)</id>
        <version>$($PSModuleInfo.Version)</version>
        <authors>$(Get-EscapedString -ElementValue $PSModuleInfo.Author)</authors>
        <owners>$(Get-EscapedString -ElementValue $PSModuleInfo.CompanyName)</owners>
        <description>$(Get-EscapedString -ElementValue $PSModuleInfo.Description)</description>
        <releaseNotes>$(Get-EscapedString -ElementValue $ReleaseNotes)</releaseNotes>
        <copyright>$(Get-EscapedString -ElementValue $PSModuleInfo.Copyright)</copyright>
        <tags>$(if($Tag){ Get-EscapedString -ElementValue ($Tag -join ' ')})</tags>
        $(if($LicenseUri){
        "<licenseUrl>$(Get-EscapedString -ElementValue $LicenseUri)</licenseUrl>
        <requireLicenseAcceptance>true</requireLicenseAcceptance>"
        })
        $(if($ProjectUri){
        "<projectUrl>$(Get-EscapedString -ElementValue $ProjectUri)</projectUrl>"
        })
        $(if($IconUri){
        "<iconUrl>$(Get-EscapedString -ElementValue $IconUri)</iconUrl>"
        })
        <dependencies>
        </dependencies>
    </metadata>
</package>
"@


    try
    {        
        $NupkgPath = "$($PSModuleInfo.ModuleBase)\$($PSModuleInfo.Name).$($PSModuleInfo.Version.ToString()).nupkg"
        $NuspecPath = "$($PSModuleInfo.ModuleBase)\$($PSModuleInfo.Name).nuspec"

        # Remove existing nuspec and nupkg files
        Microsoft.PowerShell.Management\Remove-Item $NupkgPath  -Force -ErrorAction SilentlyContinue -WarningAction SilentlyContinue -Confirm:$false -WhatIf:$false
        Microsoft.PowerShell.Management\Remove-Item $NuspecPath -Force -ErrorAction SilentlyContinue -WarningAction SilentlyContinue -Confirm:$false -WhatIf:$false
            
        Microsoft.PowerShell.Management\Set-Content -Value $nuspec -Path $NuspecPath

        # Create .nupkg file
        $output = & $script:NuGetClient pack $NuspecPath -OutputDirectory $PSModuleInfo.ModuleBase
        if($LASTEXITCODE)
        {
            $message = $LocalizedData.FailedToCreateCompressedModule -f ($output) 
            Write-Error -Message $message -ErrorId "FailedToCreateCompressedModule" -Category InvalidOperation
            return
        }

        # Publish the .nupkg to gallery
        $output = & $script:NuGetClient push $NupkgPath  -source $PSGalleryPublishUri -NonInteractive -ApiKey $NugetApiKey 
        if($LASTEXITCODE)
        {
            $message = $LocalizedData.FailedToPublish -f ($output) 
            Write-Error -Message $message -ErrorId "FailedToPublishTheModule" -Category InvalidOperation
        }
        else
        {
            $message = $LocalizedData.PublishedSuccessfully -f ($PSModuleInfo.Name) 
            Write-Verbose -Message $message
        }
    }
    finally
    {
        Microsoft.PowerShell.Management\Remove-Item $NupkgPath  -Force -ErrorAction SilentlyContinue -WarningAction SilentlyContinue -Confirm:$false -WhatIf:$false
        Microsoft.PowerShell.Management\Remove-Item $NuspecPath -Force -ErrorAction SilentlyContinue -WarningAction SilentlyContinue -Confirm:$false -WhatIf:$false
    }
}

Export-ModuleMember -Function * -Alias * -Variable PSGallerySourceUri,PSGalleryPublishUri