Import-Package.psm1
# Initialize - Bootstraps the nuget type system Write-Verbose "[Import-Package:Init] Initializing..." $bootstrapper = & (Resolve-Path "$PSScriptRoot\packaging.ps1") $loaded = @{ "NuGet.Frameworks" = "netstandard2.0" } $mutexes = @{} New-Item (Join-Path $PSScriptRoot "Packages") -Force -ItemType Directory New-Item (Join-Path $PSScriptRoot "Temp") -Force -ItemType Directory & { # Clear the Cache Write-Verbose "[Import-Package:Init] Clearing Temp Files..." Resolve-Path (Join-Path $PSScriptRoot "Temp" "*") | ForEach-Object -Parallel { $id = $_ | Split-Path -Leaf [bool] $freed = $false $m = New-Object System.Threading.Mutex( $false, "Global\ImportPackage-$id", [ref] $freed ) If( $freed ){ Remove-Item $_ -Recurse -ErrorAction Stop } $m.Dispose() } -ThrottleLimit 12 -AsJob | Out-Null } . "$PSScriptRoot\src\ConvertTo-SemVerObject.ps1" . "$PSScriptRoot\src\Resolve-DependencyVersions.ps1" . "$PSScriptRoot\src\Resolve-CachedPackage.ps1" . "$PSScriptRoot\src\Build-PackageData.ps1" Write-Verbose "[Import-Package:Init] Initialized" <# .Synopsis A simplistic cmdlet for getting the target framework of the current PowerShell session. .Description This cmdlet is used to get the target framework of the current PowerShell session. It is used by Import-Package to determine which dlls to load from a NuGet package into the session. .Example Get-Dotnet # Example Return: Net,Version=v4.7.2 #> function Get-Dotnet { [CmdletBinding()] param() Process { $bootstrapper.system } } <# .Synopsis A simplistic cmdlet for getting the target runtime of the current PowerShell session. .Description This cmdlet is used to get the target runtime of the current PowerShell session. It is used by Import-Package to determine which dlls to load from a NuGet package into the session. .Example Get-Runtime # Example Return: win10-x64 #> function Get-Runtime { [CmdletBinding()] param() Process { $bootstrapper.runtime } } <# .Synopsis Imports NuGet/Nupkg packages downloaded by PackageManagement .Description PackageManagement's default package providers (NuGet and PowerShellGet/Gallery) lack the ability to load their NuGet Packs into PowerShell. While PowerShellGallery Packages can be loaded with `Import-Module`, they import the packs using a module manifest, not the actual nuspec. This module provides a `Import-Package` cmdlet for importing packages by the nuspec instead of the module manifest. By offering the commands `Import-Package` and `Import-Module` separately, this module allows you to handle the C# dependencies (.nuspec) and PowerShell dependencies (.psd1) from the same pack file on their own. This is useful for dependency control. A couple of use cases for this feature: - You want to rewrite an existing powershell module using the same C# dependencies, but you want to provide a different PowerShell API. - You are using multiple PowerShell modules that depend on the same C# dependencies, and don't want to load the same C# dependencies multiple times. - You want to inject your own C# dependencies into a PowerShell module. .Parameter Name The name of the package to import. Alias: PackageName ParameterSetName: Managed (default) .Parameter Provider The name of the PackageManagement Provider to use. Defaults to 'NuGet' as PowerShellGallery modules can already be imported with Import-Module. Alias: ProviderName, PackageProvider ParameterSetName: Managed (default) .Parameter Version The version of the package to import. Defaults to the latest version. ParameterSetName: Managed (default) .Parameter Package The SoftwareIdentity object of the package to import (returned by Get-Package) ParameterSetName: Managed-Object .Parameter Path The path to the .nupkg file to import. Alias: PackagePath ParameterSetName: Unmanaged .Parameter TargetFramework The target framework of the package to import. Defaults to TFM of the current PowerShell session. .Parameter Offline Skip downloading the package from the package provider. .Parameter TempPath The directory to place and load native dlls from. Defaults to the current directory. .Notes You can set DIS_AUTOUPDATE_IMPORTS to 1 as an environment variable (or to $true as a global variable) to disable automatic update the Import-Package cmdlet's dependencies. .Example # These are the actual packages that make up the foundation of this module. Import-Package -Package 'NuGet.Frameworks' -TargetFramework 'netstandard2.0' Import-Package -Package 'NuGet.Packaging' -TargetFramework 'netstandard2.0' #> function Import-Package { [CmdletBinding(DefaultParameterSetName='Managed')] param( # Gets .nupkg from PackageManagement by name [Parameter( Mandatory=$true, ParameterSetName='Managed', ValueFromPipeline=$true, Position=0 )] [Alias("PackageName")] [string] $Name, [Parameter( ParameterSetName='Managed', ValueFromPipeline=$true )] [string] $Version, [Parameter( ParameterSetName='Managed', ValueFromPipeline=$true )] [Alias("ProviderName","PackageProvider")] [string] $Provider = 'NuGet', # Gets .nupkg from PackageManagement by the SoftwareIdentity object [Parameter( Mandatory=$true, ParameterSetName='Managed-Object', ValueFromPipeline=$true, Position=0 )] [Microsoft.PackageManagement.Packaging.SoftwareIdentity] $Package, $TargetFramework = (Get-Dotnet), # Gets .nupkg from the filesystem [Parameter( Mandatory=$true, ParameterSetName='Unmanaged', ValueFromPipeline=$true, Position=0 )] [Alias("PackagePath","Source")] [string] $Path, [switch] $Offline, [string] $CachePath = "$PSScriptRoot\Packages", [string] $TempPath ) Process { $temp_path_generated = If( [string]::IsNullOrWhiteSpace( $TempPath ) ){ $TempPath = & { $parent = & { Join-Path ($CachePath | Split-Path -Parent) "Temp" } [string] $uuid = [System.Guid]::NewGuid() # Cut dirname in half by compressing the UUID from base16 (hexadecimal) to base36 (alphanumeric) $id = & { $bigint = [uint128]::Parse( $uuid.ToString().Replace("-",""), 'AllowHexSpecifier') $compressed = "" # Make hex-string more compressed by encoding it in base36 (alphanumeric) $chars = "0123456789abcdefghijklmnopqrstuvwxyz" While( $bigint -gt 0 ){ $remainder = $bigint % 36 $compressed = $chars[$remainder] + $compressed $bigint = $bigint/36 } Write-Verbose "[Import-Package:Preparation] UUID $uuid (base16) converted to $compressed (base$( $chars.Length ))" $compressed } $mutexes."$id" = New-Object System.Threading.Mutex($true, "Global\ImportPackage-$id") # Lock the directory from automatic removal New-Item -ItemType Directory -Path (Join-Path $parent $id) -Force # Resolve-Path "." } $true } Else { $false } $PackageData = Switch( $PSCmdlet.ParameterSetName ){ "Managed-Object" { Write-Verbose "[Import-Package:ParameterSet] Managed Object" Build-PackageData -From "Object" -Options @( $Package, @{ "CachePath" = $CachePath "TempPath" = $TempPath }) -Bootstrapper $bootstrapper } "Managed" { Write-Verbose "[Import-Package:ParameterSet] Managed" Build-PackageData -From "Install" -Options @{ "CachePath" = $CachePath "TempPath" = $TempPath "Offline" = $Offline # If true, do not install "Name" = $Name "Version" = $Version } -Bootstrapper $bootstrapper } "Unmanaged" { Write-Verbose "[Import-Package:ParameterSet] Unmanaged" Build-PackageData -From "File" -Options @{ "CachePath" = $CachePath "TempPath" = $TempPath "Source" = $Path } -Bootstrapper $bootstrapper } } If( $PackageData ){ Write-Verbose "[Import-Package:Preparation] Package $($PackageData.Name)$( If( $PackageData.Version ) { " $( $PackageData.Version ) successfully read"})" } Else { Write-Host "[Import-Package:Preparation] Package $($PackageData.Name) was skipped!" } Write-Verbose "[Import-Package:Framework-Handling] Selecting best available framework from package $($PackageData.Name)" $TargetFramework = $TargetFramework -as [NuGet.Frameworks.NuGetFramework] $TargetFramework = & { If( $PackageData.Frameworks ){ $parsed_frameworks = $PackageData.Frameworks | ForEach-Object { # $PackageData.Frameworks is in ShortFolderName (or TFM) format, it needs to be converted [NuGet.Frameworks.NuGetFramework]::Parse( $_ ) } $nearest_framework = Switch( $parsed_frameworks.Count ){ 0 { $TargetFramework } # Fallback to the user provided one 1 { $bootstrapper.Reducer.GetNearest( $TargetFramework, [NuGet.Frameworks.NuGetFramework[]]@( $parsed_frameworks ) ) } default { $bootstrapper.Reducer.GetNearest( $TargetFramework, [NuGet.Frameworks.NuGetFramework[]]$parsed_frameworks ) } } If( $nearest_framework ){ $nearest_framework } Else { $TargetFramework } } Elseif ( $PackageData.RID_Frameworks ){ $parsed_frameworks = $PackageData.RID_Frameworks | ForEach-Object { # $PackageData.RID_Frameworks is in ShortFolderName (or TFM) format, it needs to be converted [NuGet.Frameworks.NuGetFramework]::Parse( $_ ) } $nearest_framework = Switch( $parsed_frameworks.Count ){ 0 { $TargetFramework } # Fallback to the user provided one 1 { $bootstrapper.Reducer.GetNearest( $TargetFramework, [NuGet.Frameworks.NuGetFramework[]]@( $parsed_frameworks )) } default { $bootstrapper.Reducer.GetNearest( $TargetFramework, [NuGet.Frameworks.NuGetFramework[]]$parsed_frameworks ) } } If( $nearest_framework ){ $nearest_framework } Else { $TargetFramework } } Else { # Fallback to the user provided one $TargetFramework } } $target_rid_framework = If( -not $PackageData.Frameworks ){ $TargetFramework } Elseif ( $PackageData.RID_Frameworks ){ $parsed_frameworks = $PackageData.RID_Frameworks | ForEach-Object { # $PackageData.RID_Frameworks is in ShortFolderName (or TFM) format, it needs to be converted [NuGet.Frameworks.NuGetFramework]::Parse( $_ ) } $nearest_framework = Switch( $parsed_frameworks.Count ){ 0 { $TargetFramework } # Fallback to the user provided one 1 { $bootstrapper.Reducer.GetNearest( $TargetFramework, [NuGet.Frameworks.NuGetFramework[]]@( $parsed_frameworks ) ) } default { $bootstrapper.Reducer.GetNearest( $TargetFramework, [NuGet.Frameworks.NuGetFramework[]]$parsed_frameworks ) } } If( $nearest_framework ){ $nearest_framework } Else { $TargetFramework } } If( -not $target_rid_framework ){ $target_rid_framework = $TargetFramework } Write-Verbose "[Import-Package:Framework-Handling] Selected OS-agnostic framework $TargetFramework" Write-Verbose "[Import-Package:Framework-Handling] Selected OS-specific framework $target_rid_framework" If( $PackageData.Dependencies -and -not $PackageData.Unmanaged ){ Write-Verbose "[Import-Package:Dependency-Handling] Loading dependencies for $( $PackageData.Name )" If( $PackageData.Dependencies.Agnostic ){ $package_framework = $TargetFramework $PackageData.Dependencies.Agnostic | ForEach-Object { If( $loaded[ $_.Name ] ){ Write-Verbose "[Import-Package:Dependency-Handling] ($($PackageData.Name) Dependency) $($_.Name) already loaded" } Else { Write-Verbose "[Import-Package:Dependency-Handling] ($($PackageData.Name) Dependency) Loading $($_.Name) - $($_.Version) (Framework $( $package_framework.GetShortFolderName() ))" If( $PackageData.Offline ){ Import-Package $_.Name -Version $_.Version -TargetFramework $package_framework -TempPath $PackageData.TempPath -Offline } Else { Import-Package $_.Name -Version $_.Version -TargetFramework $package_framework -TempPath $PackageData.TempPath } Write-Verbose "[Import-Package:Dependency-Handling] ($($PackageData.Name) Dependency) $($_.Name) Loaded" } } } If( $PackageData.Dependencies.ByFramework ){ $package_framework = & { $parsed_frameworks = $PackageData.Dependencies.ByFramework.Keys -as [NuGet.Frameworks.NuGetFramework[]] $selected_framework = $bootstrapper.Reducer.GetNearest( $TargetFramework, $parsed_frameworks ) $unparsed_selected_framework = $PackageData.Dependencies.ByFramework.Keys | Where-Object { ([NuGet.Frameworks.NuGetFramework] $_).ToString() -eq ($selected_framework).ToString() } $unparsed_selected_framework } $PackageData.Dependencies.ByFramework[ $package_framework ] | ForEach-Object { If( $loaded[ $_.Name ] ){ Write-Verbose "[Import-Package:Dependency-Handling] ($($PackageData.Name) Dependency) $($_.Name) already loaded" } Else { Write-Verbose "[Import-Package:Dependency-Handling] ($($PackageData.Name) Dependency) Loading $($_.Name) - $($_.Version) (Framework $( ([NuGet.Frameworks.NuGetFramework]$package_framework).GetShortFolderName() ))" If( $PackageData.Offline ){ Import-Package $_.Name -Version $_.Version -TargetFramework $package_framework -TempPath $PackageData.TempPath -Offline } Else { Import-Package $_.Name -Version $_.Version -TargetFramework $package_framework -TempPath $PackageData.TempPath } Write-Verbose "[Import-Package:Dependency-Handling] ($($PackageData.Name) Dependency) $($_.Name) Loaded" } } } } Elseif( $PackageData.Dependencies.Count ) { Write-Warning "[Import-Package:Dependency-Handling] $($PackageData.Name) has $($PackageData.Dependencies.Count) dependencies, but has been marked for unmanaged loading. Make sure to load the dependencies manually" } $dlls = @{ "lib" = [System.Collections.ArrayList]::new() } Write-Verbose "[Import-Package:Loading] Locating OS-agnostic dlls" $short_folder_name = $TargetFramework.GetShortFolderName() If( Test-Path "$(Split-Path $PackageData.Source)\lib" ){ Write-Verbose "[Import-Package:Loading] Locating OS-agnostic framework-agnostic dlls" Try { $agnostic_dlls = Resolve-Path "$(Split-Path $PackageData.Source)\lib\*.dll" -ErrorAction Stop Switch( $agnostic_dlls.Count ){ 0 {} 1 { $dlls.lib.Add( $agnostic_dlls ) | Out-Null } default { $dlls.lib.AddRange( $agnostic_dlls ) | Out-Null } } Write-Verbose "[Import-Package:Loading] Found $( $dlls.lib.Count ) OS-agnostic framework-agnostic dlls" } Catch { Write-Verbose "[Import-Package:Loading] Unable to find OS-agnostic framework-agnostic dlls for $($PackageData.Name)" } If( Test-Path "$(Split-Path $PackageData.Source)\lib\$short_folder_name" ){ Write-Verbose "[Import-Package:Loading] Locating OS-agnostic dlls for $short_folder_name" Try { $framework_dlls = Resolve-Path "$(Split-Path $PackageData.Source)\lib\$short_folder_name\*.dll" -ErrorAction Stop Switch( $framework_dlls.Count ){ 0 {} 1 { $dlls.lib.Add( $framework_dlls ) | Out-Null } default { $dlls.lib.AddRange( $framework_dlls ) | Out-Null } } Write-Verbose "[Import-Package:Loading] Found $( $dlls.lib.Count ) OS-agnostic dlls for $short_folder_name" } Catch { Write-Verbose "[Import-Package:Loading] Unable to find OS-agnostic dlls for $($PackageData.Name) for $short_folder_name" } } } Write-Verbose "[Import-Package:Loading] Locating OS-specific dlls" $short_folder_name = $target_rid_framework.GetShortFolderName() If( Test-Path "$(Split-Path $PackageData.Source)\runtimes\$( $PackageData.RID )" ){ $dlls.runtime = [System.Collections.ArrayList]::new() Try { $native_dlls = Resolve-Path "$(Split-Path $PackageData.Source)\runtimes\$( $PackageData.RID )\native\*.dll" -ErrorAction Stop Switch( $native_dlls.Count ){ 0 {} 1 { $dlls.runtime.Add( $native_dlls ) | Out-Null } default { $dlls.runtime.AddRange( $native_dlls ) | Out-Null } } Write-Verbose "[Import-Package:Loading] Found $( $native_dlls.Count ) OS-specific native dlls" } Catch { Write-Verbose "[Import-Package:Loading] Unable to find OS-specific native dlls for $($PackageData.Name) on $($bootstrapper.runtime)" } If( Test-Path "$(Split-Path $PackageData.Source)\runtimes\$( $PackageData.RID )\lib\$short_folder_name" ){ Try { $lib_dlls = Resolve-Path "$(Split-Path $PackageData.Source)\runtimes\$( $PackageData.RID )\lib\$short_folder_name\*.dll" -ErrorAction Stop Switch( $lib_dlls.Count ){ 0 {} 1 { $dlls.runtime.Add( $lib_dlls ) | Out-Null } default { $dlls.runtime.AddRange( $lib_dlls ) | Out-Null } } Write-Verbose "[Import-Package:Loading] Found $( $lib_dlls.Count ) OS-specific managed dlls" } Catch { Write-Verbose "[Import-Package:Loading] Unable to find OS-specific managed dlls for $($PackageData.Name) on $($bootstrapper.runtime)" } } } $loaded[ $PackageData.Name ] = @( $TargetFramework.GetShortFolderName(), $target_rid_framework.GetShortFolderName() ) if ( $dlls.lib -or $dlls.runtime ) { if( $dlls.lib ){ $dlls.lib | ForEach-Object { $dll = $_ Try { Import-Module $_ -ErrorAction Stop } Catch { Write-Error "[Import-Package:Loading] Unable to load 'lib' dll ($($dll | Split-Path -Leaf)) for $($PackageData.Name)`n$($_.Exception.Message)`n" If( $PackageData.Unmanaged ){ Write-Host Write-Host "[Import-Package:Loading] Package $($PackageData.Name) is marked for Unmanaged loading, which requires dependencies to be imported manually." Write-Host "- Did you forget a dependency?" -ForegroundColor Yellow } $_.Exception.GetBaseException().LoaderExceptions | ForEach-Object { Write-Host $_.Message } return } } } if( $dlls.runtime ){ $dlls.runtime | ForEach-Object { $dll = $_ Try { If( $bootstrapper.TestNative( $_.ToString() ) ){ Write-Verbose "[Import-Package:Loading] $_ is a native dll for $($PackageData.Name)" Write-Verbose "- Moving to '$TempPath'" $bootstrapper.LoadNative( $_.ToString(), $TempPath ) | ForEach-Object { Write-Verbose "[Import-Package:Loading] Dll retunrned leaky handle $_"} } Else { Write-Verbose "[Import-Package:Loading] $_ is not native, but is a OS-specific dll for $($PackageData.Name)" Import-Module $_ } } Catch { Write-Error "[Import-Package:Loading] Unable to load 'runtime' dll ($($dll | Split-Path -Leaf)) for $($PackageData.Name) for $($bootstrapper.runtime)`n$($_.Exception.Message)`n" If( $PackageData.Unmanaged ){ Write-Host Write-Host "[Import-Package:Loading] Package $($PackageData.Name) is marked for Unmanaged loading, which requires dependencies to be imported manually." Write-Host "- Did you forget a dependency?" -ForegroundColor Yellow } return } } } } else { Write-Warning "[Import-Package:Loading] $($PackageData.Name) is not needed for $( $bootstrapper.Runtime )`:$($TargetFramework.GetShortFolderName())" } If( $temp_path_generated ){ If( Test-Path (Join-Path $TempPath "*") ){ Write-Verbose "[Import-Package:Loading] Temp files: $TempPath" } Else { Remove-Item -Path $TempPath -ErrorAction Stop } } } } <# .Synopsis Reads the nuspec of a NuGet/Nupkg package downloaded by PackageManagement .Description Provides a way to read the nuspec of a NuGet/Nupkg package downloaded by PackageManagement. .Parameter Name The name of the package to read. Alias: PackageName ParameterSetName: Managed (default) .Parameter Provider The name of the PackageManagement Provider to use. Defaults to 'NuGet' (consistent with Import-Package). Alias: ProviderName, PackageProvider ParameterSetName: Managed (default) .Parameter Version The version of the package to read. Defaults to the latest version. .Parameter Package The SoftwareIdentity object of the package to read (returned by Get-Package) ParameterSetName: Managed-Object .Parameter Path The path to the .nupkg file to import. Alias: PackagePath ParameterSetName: Unmanaged .Example Read-Package -Package 'NuGet.Frameworks' Read-Package -Package 'NuGet.Packaging' #> function Read-Package { [CmdletBinding(DefaultParameterSetName='Managed')] param( # Gets .nupkg from PackageManagement by name [Parameter( Mandatory=$true, ParameterSetName='Managed', ValueFromPipeline=$true, Position=0 )] [Alias("PackageName")] [string] $Name, [Parameter( ParameterSetName='Managed', ValueFromPipeline=$true )] [string] $Version, [Parameter( ParameterSetName='Managed', ValueFromPipeline=$true )] # Gets .nupkg from PackageManagement by the SoftwareIdentity object [Alias("ProviderName","PackageProvider")] [string] $Provider = 'NuGet', [Parameter( Mandatory=$true, ParameterSetName='Managed-Object', ValueFromPipeline=$true, Position=0 )] [Microsoft.PackageManagement.Packaging.SoftwareIdentity] $Package, # Gets .nupkg from the filesystem [Parameter( Mandatory=$true, ParameterSetName='Unmanaged', ValueFromPipeline=$true, Position=0 )] [Alias("PackagePath")] [string] $Path ) Process { $Path = if( $PSCmdlet.ParameterSetName -eq "Managed" ){ (Get-Package $Name -RequiredVersion $Version -ProviderName $Provider -ErrorAction Stop).Source } elseif( $PSCmdlet.ParameterSetName -eq "Unmanaged" ){ $Path } else { $Package.Source } $bootstrapper.ReadNuspec( $Path ) } } If( ($bootstrapper.Runtime -match "^win") -and ($bootstrapper.System.Framework -eq ".NETCoreApp") ){ # Automatically fixes the missing WinRT functionality in PowerShell Core on Windows If( ($global:DIS_AUTOUPDATE_IMPORTS -eq $true ) -or ( $env:DIS_AUTOUPDATE_IMPORTS -eq 1 ) ){ Import-Package "Microsoft.Windows.SDK.NET.Ref" -Offline } Else { Import-Package "Microsoft.Windows.SDK.NET.Ref" } } Export-ModuleMember -Function @( "Import-Package", "Read-Package", "Get-Dotnet", "Get-Runtime" ) |