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 -ErrorAction SilentlyContinue #needed by Import-Package for RID detection | |
$Bootstrapper = New-Object psobject | |
try { | |
Add-Type -AssemblyName System.Reflection -ErrorAction SilentlyContinue | |
Add-Type -AssemblyName System.Runtime.Loader -ErrorAction SilentlyContinue | |
$Bootstrapper | Add-Member ` | |
-MemberType NoteProperty ` | |
-Name Context ` | |
-Value [System.Runtime.Loader.AssemblyLoadContext]::new( "", $true ) | |
$Bootstrapper | Add-Member ` | |
-MemberType ScriptMethod ` | |
-Name Unload ` | |
-Value { | |
$This.Context.Unload() | |
} | |
} catch { } | |
$Bootstrapper | Add-Member ` | |
-Name GetLatest ` | |
-MemberType ScriptMethod ` | |
-Value { | |
param( $name ) | |
$apis = Invoke-WebRequest https://api.nuget.org/v3/index.json | |
$apis = $this.Deserialize( $apis.ToString(), [hashtable] ) | |
$resources = If( $apis.resources.GetType().Name -eq "JsonElement" ){ | |
$apis.resources.EnumerateArray() | ForEach-Object { | |
$output = @{} | |
$_.EnumerateObject() | ForEach-Object { | |
$output[ $_.Name ] = $_.Value.ToString() | |
} | |
$output | |
} | |
} else { | |
$apis.resources | |
} | |
$resource = $resources | Where-Object { | |
($_."@type" -eq "SearchQueryService") -and | |
($_."comment" -like "*(primary)*") | |
} | Select-Object -First 1 | |
$id = $resource."@id" | |
$results = Invoke-WebRequest "$id`?q=packageid:$Name&prerelease=false&take=1" | |
$results = $this.Deserialize( $results.ToString(), [hashtable] ) | |
$metadata = $results.data[0] | |
if( $metadata.GetType().Name -eq "JsonElement" ){ | |
($metadata.EnumerateObject() | Where-Object { $_.Name -eq "version" }).Value.ToString() | |
} else { | |
$metadata.version | |
} | |
} | |
$Bootstrapper | Add-Member ` | |
-Name Deserialize ` | |
-MemberType ScriptMethod ` | |
-Value (& { | |
Try { | |
Add-Type -AssemblyName System.Text.Json -ErrorAction SilentlyContinue | |
if( [System.Text.Json.JsonSerializer]::Deserialize ){ | |
{ | |
param( $text, $type = [hashtable] ) | |
[System.Text.Json.JsonSerializer]::Deserialize( $text, [hashtable] ) | |
} | |
} | |
} Catch { | |
Try { | |
Add-Type -AssemblyName System.Web.Extensions -ErrorAction SilentlyContinue | |
if( [System.Web.Script.Serialization.JavaScriptSerializer] ){ | |
{ | |
param( $text, $type = [hashtable] ) | |
[System.Web.Script.Serialization.JavaScriptSerializer]::new().Deserialize( $text, [hashtable] ) | |
} | |
} | |
} Catch { | |
{ | |
param( $text, $type ) | |
if( $type = [hashtable] ){ | |
Try { | |
ConvertFrom-Json $text -AsHashtable | |
} Catch { | |
ConvertFrom-Json $text | |
} | |
} else { | |
ConvertFrom-Json $text | |
} | |
} | |
} | |
} | |
}) | |
Add-Type -assembly "system.io.compression.filesystem" | |
$Bootstrapper | Add-Member ` | |
-MemberType ScriptMethod ` | |
-Name GetContentsFromZip ` | |
-Value { | |
param( $Archive, $Path ) | |
$zip = [io.compression.zipfile]::OpenRead($Archive) | |
$file = $zip.Entries | where-object { $_.FullName -eq $Path } | |
$stream = $file.Open() | |
$reader = New-Object IO.StreamReader( $stream ) | |
$content = $reader.ReadToEnd() | |
$content | |
$reader.Close() | Out-Null | |
$stream.Close() | Out-Null | |
$zip.Dispose() | Out-Null | |
} | |
$Bootstrapper | Add-Member ` | |
-MemberType ScriptMethod ` | |
-Name Load ` | |
-Value { | |
param( | |
[string] $Path, | |
[bool] $Partial = $false | |
) | |
try { | |
If( $Partial ){ | |
$This.Context.LoadFromAssemblyName( [System.Reflection.AssemblyName]::new( $Path ) ) | Out-Null | |
} else { | |
$This.Context.LoadFromAssemblyPath( $Path ) | Out-Null | |
} | |
} catch { | |
try { | |
$AddTypeParams = @{ | |
PassThru = $false | |
} | |
if( $Partial ) { | |
$AddTypeParams.AssemblyName = $Path | |
} else { | |
$AddTypeParams.Path = $Path | |
} | |
Add-Type @AddTypeParams | |
} catch { Write-Host "Unable to load $AssemblyName" } | |
} | |
} | |
$Bootstrapper | Add-Member ` | |
-MemberType ScriptMethod ` | |
-Name ParseTypeName ` | |
-Value { | |
param( | |
[string] $TypeName | |
) | |
try { | |
$Assemblies = $This.Context.Assemblies | Where-Object { $ _ } | |
$Assemblies.GetType( $TypeName, $false, $true ) | |
} catch { | |
try { | |
[Type]"$TypeName" | |
} catch { $null } | |
} | |
} | |
$Bootstrapper | Add-Member ` | |
-MemberType ScriptMethod ` | |
-Name Init ` | |
-Value { | |
If( -not( $This.ParseTypeName( "NuGet.Packaging.PackageArchiveReader" ) ) ){ | |
$time_ordergen = Measure-Command { | |
$load_order = [System.Collections.ArrayList]::new() | |
$load_order.Add( "NuGet.Packaging" ) | Out-Null | |
# Loop initialization: | |
$index = 0 | |
$package_name = "" | |
# Caching for performance: | |
$package_table = @{} | |
while( $index -lt $load_order.Count ){ | |
$package_name = $load_order[ $index ] | |
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() | |
# Get .NETStandard2.0 dependencies | |
$dependencies = ( | |
([xml] $bootstrapper.GetContentsFromZip( | |
$package_table[ $package_name ], | |
"$package_name.nuspec" | |
)).package.metadata.dependencies.group | | |
Where-Object { $_.targetFramework -eq ".NETStandard2.0" } | |
).Dependency | |
$dependencies = $dependencies | Where-Object { $_.Id } | |
$dependencies | | |
ForEach-Object { | |
$load_order.Add( $_.Id ) | Out-Null | |
} | |
} else { | |
$oldindex = $load_order.IndexOf( $package_name ) | |
$load_order.RemoveAt( $oldindex ) | |
$load_order.Add( $package_name ) | |
} | |
$index++ | |
} | |
$this | Add-Member ` | |
-MemberType NoteProperty ` | |
-Name Dependencies ` | |
-Value ($load_order | Select-Object -Unique) | Out-Null | |
[array]::Reverse( $this.Dependencies ) | |
} | |
Write-Host "Total time:" $time_ordergen.TotalSeconds | |
$this.Dependencies | ForEach-Object { | |
$package_source = $package_table[ $_ ] | |
$dll = Resolve-Path "$(Split-Path $package_source -ErrorAction SilentlyContinue)\lib\netstandard2.0\$_.dll" -ErrorAction SilentlyContinue | |
if (-not (Test-Path $dll)) { | |
Write-Host "Unable to find $_" | |
} else { | |
$this.Load( $dll.ToString() ) | |
} | |
} | |
} | |
} | |
# Return the bootstrapper, and initialize it | |
$Bootstrapper | |
$Bootstrapper.Init() | |
} |
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: