Last active
October 7, 2023 11:29
-
-
Save anonhostpi/f385e0ec2bca28cc5cc2a136627a4c69 to your computer and use it in GitHub Desktop.
NuGet.Frameworks and Microsft.NETCore.Platforms wrappers for PowerShell (bootstrap code for writing an Import-Package cmdlet)
This file contains 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
& { | |
Add-Type -AssemblyName System.Runtime # Useful for RID detection | |
Add-Type -AssemblyName System.IO.Compression.FileSystem # Useful for reading nupkg/zip files | |
# Exported methods and properties | |
$Exported = New-Object psobject | |
$Exported | Add-Member ` | |
-MemberType ScriptMethod ` | |
-Name GetLatest ` | |
-Value { | |
param( $Name ) | |
$apis = Invoke-WebRequest https://api.nuget.org/v3/index.json | |
$apis = ConvertFrom-Json $apis | |
$resource = $apis.resources | Where-Object { | |
($_."@type" -eq "SearchQueryService") -and | |
($_.comment -like "*(primary)*") | |
} | |
$id = $resource."@id" | |
$results = Invoke-WebRequest "$id`?q=packageid:$Name&prerelease=false&take=1" | |
$results = ConvertFrom-Json $results | |
$results.data[0].version | |
} | |
$Exported | Add-Member ` | |
-MemberType ScriptMethod ` | |
-Name ReadNuspec ` | |
-Value { | |
param( $Package ) | |
$nupkg = $Package.Source | |
$nupkg = [System.IO.Compression.ZipFile]::OpenRead( $nupkg ) | |
$nuspec = $nupkg.Entries | Where-Object { $_.FullName -eq "$($Package.Name).nuspec" } | |
$stream = $nuspec.Open() | |
$reader = New-Object System.IO.StreamReader( $stream ) | |
[xml]($reader.ReadToEnd()) | |
$reader.Close() | |
$stream.Close() | |
$nupkg.Dispose() | |
} | |
$Exported | Add-Member ` | |
-MemberType ScriptMethod ` | |
-Name Load ` | |
-Value { | |
param( | |
[string] $Path, | |
[bool] $Partial = $false | |
) | |
# todo: add handling of native/unmanaged assemblies | |
try { | |
$AddTypeParams = @{ | |
# PassThru = $false | |
} | |
if( $Partial ) { | |
$AddTypeParams.AssemblyName = $Path | |
} elseif ( Test-Path $Path ) { | |
$AddTypeParams.Path = $Path | |
} else { | |
Write-Host "Unable to load $Path" | |
return | |
} | |
Add-Type @AddTypeParams | |
} catch { Write-Host "Unable to load $Path" } | |
} | |
$Exported | Add-Member ` | |
-MemberType ScriptMethod ` | |
-Name Init ` | |
-Value { | |
$Loaded = Try { | |
[NuGet.Frameworks.FrameworkConstants+FrameworkIdentifiers] | |
} Catch { | |
$false | |
} | |
If( -not( $Loaded )){ | |
$load_order = [System.Collections.ArrayList]::new() | |
$load_order.Add( "NuGet.Frameworks" ) | Out-Null | |
# Loop init | |
$i = 0 | |
$package_name = "" | |
# Caching for performance | |
$package_table = @{} | |
while( $i -lt $load_order.count ){ | |
$package_name = $load_order[$i] | |
If( -not( $package_table.ContainsKey( $package_name ) ) ){ | |
$package = Get-Package $package_name -ProviderName NuGet -ErrorAction SilentlyContinue | |
$latest = Try { | |
$this.GetLatest( $package_name ) | |
} Catch { $package.Version } | |
if( (-not $package) -or ($package.Version -ne $latest) ){ | |
Try { | |
Install-Package $package_name ` | |
-ProviderName NuGet ` | |
-SkipDependencies ` | |
-Force | Out-Null | |
} Catch {} | |
$package = Get-Package $package_name -ProviderName NuGet -ErrorAction Stop | |
} | |
$package_table[ $package_name ] = $package.Source.ToString() | |
$dependencies = ($this.ReadNuspec( $package ).package.metadata.dependencies.group | Where-Object { $_.targetframework -eq "netstandard2.0" }).dependency | Where-Object { $_.id } | |
foreach( $dependency in $dependencies ){ | |
$load_order.Add( $dependency.id ) | Out-Null | |
} | |
} else { | |
$oldindex = $load_order.IndexOf( $package_name ) | |
$load_order.RemoveAt( $oldindex ) | |
$load_order.Add( $package_name ) | |
} | |
$i += 1 | |
} | |
$this | Add-Member ` | |
-MemberType NoteProperty ` | |
-Name Dependencies ` | |
-Value ($load_order | Select-Object -Unique) | |
[array]::Reverse( $this.Dependencies ) | |
$this.Dependencies | ForEach-Object { | |
$package_source = $package_table[ $_ ] | |
$dll = Resolve-Path "$(Split-Path $package_source -ErrorAction SilentlyContinue)\lib\netstandard2.0\$_.dll" -ErrorAction SilentlyContinue | |
$this.Load( $dll ) | |
} | |
$this | Add-Member ` | |
-MemberType NoteProperty ` | |
-Name Reducer ` | |
-Value ([NuGet.Frameworks.FrameworkReducer]::new()) | |
$this | Add-Member ` | |
-MemberType NoteProperty ` | |
-Name Frameworks ` | |
-Value @{} | |
[NuGet.Frameworks.FrameworkConstants+FrameworkIdentifiers].DeclaredFields | ForEach-Object { | |
$this.Frameworks[$_.Name] = $_.GetValue( $null ) | |
} | |
$this | Add-Member ` | |
-MemberType NoteProperty ` | |
-Name System ` | |
-Value (& { | |
$runtime = [System.Runtime.InteropServices.RuntimeInformation, mscorlib]::FrameworkDescription | |
$version = $runtime -split " " | Select-Object -Last 1 | |
$framework_name = ($runtime -split " " | Select-Object -SkipLast 1) -join " " | |
If( $framework_name -eq ".NET Framework" ) { | |
$framework_name = "Net" | |
} else { | |
$framework_name = "NETCoreApp" | |
} | |
[NuGet.Frameworks.NuGetFramework]::new( "$($this.Frameworks[ $framework_name ]),Version=v$version" ) | |
}) | |
# .NET Core 5+ features | |
Try { | |
& { | |
If( [System.Runtime.InteropServices.RuntimeInformation]::RuntimeIdentifier ){ | |
$package = Get-Package "Microsoft.NETCore.Platforms" -ProviderName NuGet -ErrorAction SilentlyContinue | |
$latest = Try { | |
$this.GetLatest( "Microsoft.NETCore.Platforms" ) | |
} Catch { $package.Version } | |
if( (-not $package) -or ($package.Version -ne $latest) ){ | |
Try { | |
Install-Package "Microsoft.NETCore.Platforms" ` | |
-ProviderName NuGet ` | |
-SkipDependencies ` | |
-Force | Out-Null | |
} Catch {} | |
$package = Get-Package "Microsoft.NETCore.Platforms" -ProviderName NuGet -ErrorAction Stop | |
} | |
$this | Add-Member ` | |
-MemberType NoteProperty ` | |
-Name Runtimes ` | |
-Value (Get-Content "$($package.source | Split-Path )\runtime.json" -Raw | ConvertFrom-Json).runtimes | |
$this | Add-Member ` | |
-MemberType NoteProperty ` | |
-Name Runtime ` | |
-Value ([System.Runtime.InteropServices.RuntimeInformation]::RuntimeIdentifier.ToString()) | |
$this | Add-Member ` | |
-MemberType NoteProperty ` | |
-Name Graphs ` | |
-Value ([System.Collections.ArrayList]::new()) | |
$this.Graphs.Add( [System.Collections.ArrayList]::new() ) | Out-Null | |
$grapher = { | |
param( [string] $_rid, [System.Collections.ArrayList] $_graph ) | |
if( $_rid ){ | |
$_graph.Add( $_rid ) | Out-Null | |
$_kids = $this.Runtimes."$_rid".'#import' | |
If( $_kids.Count -gt 0 ){ | |
$_kids | Select-Object -Skip 1 | ForEach-Object { | |
$_clone = $_graph.Clone() | |
$this.Graphs.Add( $_clone ) | Out-Null | |
& $grapher $_ $_clone | |
} | |
} | |
& $grapher $_kids[0] $_graph | |
} | |
} | |
& $grapher $this.Runtime $this.Graphs[0] | |
} | |
} | |
} Catch {} | |
# Native/Unmanaged assemblies | |
Try { | |
$this | Add-Member ` | |
-MemberType ScriptMethod ` | |
-Name LoadNative ` | |
-Value (& { | |
Add-Type -MemberDefinition @" | |
[DllImport("kernel32")] | |
public static extern IntPtr LoadLibrary(string path); | |
[DllImport("libdl")] | |
public static extern IntPtr dlopen(string path, int flags); | |
"@ -Namespace "_Native" -Name "Loaders" | |
{ | |
param( $Path, $CopyTo ) | |
If( $CopyTo ){ | |
Copy-Item $Path $CopyTo -Force -ErrorAction SilentlyContinue | Out-Null | |
$Path = "$CopyTo\$($Path | Split-Path -Leaf)" | |
} | |
$lib_handle = [System.IntPtr]::Zero | |
If( [System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform([System.Runtime.InteropServices.OSPlatform]::Windows) ){ | |
$lib_handle = [_Native.Loaders]::LoadLibrary( $Path ) | |
} else { | |
$lib_handle = [_Native.Loaders]::dlopen( $Path, 0 ) | |
} | |
If( $lib_handle -eq [System.IntPtr]::Zero ){ | |
Throw "Unable to load $Path" | |
} | |
# BUG: Leaky handle | |
$lib_handle | |
} | |
}) | |
-Force | |
$this | Add-Member ` | |
-MemberType ScriptMethod ` | |
-Name TestNative ` | |
-Value { | |
param( $Path ) | |
try { | |
[Reflection.AssemblyName]::GetAssemblyName($Path) | Out-Null | |
return $false | |
} catch { | |
return $true | |
} | |
} | |
} Catch {} | |
} | |
$this | |
} | |
$Exported.Init() | |
} | % { $global:Test = $_ } |
Improvement 2:
-
Refactored self-initialization
-
Added new properties to exported object:
- Reducer: A default framework reducer (used for determining best-fit framework in array of frameworks for a desired platform)
- Frameworks: A hashtable representation of NuGet's Framework Constants
- System: The framework of the powershell session
- .NET Core 5+ Properties:
- Runtimes: A json-psobject Runtime ID graph provided by Microsoft.NETCore.Platforms > runtimes.json
- Runtime: the Runtime ID of the powershell session
- Graphs: pre-parsed graphs for the Runtime ID of the powershell session
-
Revisions:
Improvement 3:
- Add support for native/unmanaged libraries
- Add a LoadNative method and a TestNative method for handling native libraries
- Revisions:
Improvement 4:
-
Split packaging, platform, and runtimeidentifier functionality into 3 files
- reason for doing this is that I can see others finding my code for powershell platform detection useful
- one of the files is a wrapper for the Microsoft.NETCore.Platforms library and the other new file is a custom implementation of RuntimeInformation.RuntimeIdentifier for non-".NET Core" platforms
-
Added custom implementation of RuntimeInformation.RuntimeIdentifier
-
Revisions:
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Optimization 1: