Skip to content

Instantly share code, notes, and snippets.

@anonhostpi
Last active October 7, 2023 11:29
Show Gist options
  • Save anonhostpi/f385e0ec2bca28cc5cc2a136627a4c69 to your computer and use it in GitHub Desktop.
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)
& {
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" )
})
# RuntimeIdentifier Handling
Try {
& "$PSScriptRoot/platforms.ps1" $this | Out-Null
} 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 = $_ }
param(
[parameter(Mandatory = $true)]
[psobject]
$Exported
)
& {
## Get all NuGet and Microsoft Supported RIDs from Microsoft.NETCore.Platforms
$package = Get-Package "Microsoft.NETCore.Platforms" -ProviderName NuGet -ErrorAction SilentlyContinue
$latest = Try {
# $Exported.GetLatest( "Microsoft.NETCore.Platforms" )
& {
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
} "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
}
$Exported | Add-Member `
-MemberType NoteProperty `
-Name Runtimes `
-Value (Get-Content "$($package.source | Split-Path )\runtime.json" -Raw | ConvertFrom-Json).runtimes
$grapher = {
param( [string] $_rid, [System.Collections.ArrayList] $_graph, [System.Collections.ArrayList] $_graphs = $Exported.Graphs )
if( $_rid ){
$_graph.Add( $_rid ) | Out-Null
$_kids = $Exported.Runtimes."$_rid".'#import'
If( $_kids.Count -gt 0 ){
$_kids | Select-Object -Skip 1 | ForEach-Object {
$_clone = $_graph.Clone()
$_graphs.Add( $_clone ) | Out-Null
& $grapher $_ $_clone $_graphs
}
}
& $grapher $_kids[0] $_graph $_graphs
}
}
& "$PSScriptRoot\runtimeidentifier.ps1" $Exported | Out-Null
$Exported | Add-Member `
-MemberType NoteProperty `
-Name Graphs `
-Value ([System.Collections.ArrayList]::new())
$Exported.Graphs.Add( [System.Collections.ArrayList]::new() ) | Out-Null
& $grapher $Exported.Runtime $Exported.Graphs[0]
$Exported
}
param(
[parameter(Mandatory = $true)]
[psobject]
$Exported
)
Try {
$Exported | Add-Member `
-MemberType NoteProperty `
-Name Runtime `
-Value ([System.Runtime.InteropServices.RuntimeInformation]::RuntimeIdentifier.ToString())
} Catch {
# The C# Architecture Enum should cover all architectures listed in Microsoft.NETCore.Platforms
# - see: https://learn.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.architecture?view=net-7.0
$arch = Try {
[System.Runtime.InteropServices.RuntimeInformation]::ProcessArchitecture.ToString()
} Catch {
Try {
[System.Runtime.InteropServices.RuntimeInformation, mscorlib]::OSArchitecture.ToString()
} Catch {}
}
$os_releases_file = Get-Content -Path "/etc/os-release" -ErrorAction SilentlyContinue
$os_checks = @{
# Standard Linux Checks
@(
"alpine", "arch", "centos", "debian", "exherbo", "fedora",
"gentoo", "linuxmint", "manjaro", "miraclelinux", "ol",
"opensuse", "rhel", "rocky", "sles", "tizen", "ubuntu"
) = { param( $OS ); Try { $os_releases_file -match "ID\=[`"']?$OS" } Catch { $false } }
# Unsupported OS Checks
@(
"android", # Android currently requires Xamarin/.NET MAUI to run, and PowerShell only supports .NET Core and .NET Framework
"browser", # While Blazor Web Assembly technically has limited capability to run .NET Core, there is no current implementation of PowerShell for it
"illumos", "omnios", "openindiana", "smartos", "solaris", # Solaris systems are not supported by PowerShell
"ios", "iossimulator", "tvos", "tvossimulator", # iOS currently requires Xamarin/.NET MAUI to run, and PowerShell only supports .NET Core and .NET Framework
"maccatalyst", # While maccatalyst supports .NET core, there is no current implementation of PowerShell for it
"freebsd" # FreeBSD does not yet support .NET Core
) = { $false }
# Built-in Checks
@( "osx", "linux" ) = { param( $OS ); [System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform([System.Runtime.InteropServices.OSPlatform]::"$OS") }
"win" = { [System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform([System.Runtime.InteropServices.OSPlatform]::Windows) }
"unix" = {
[System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform([System.Runtime.InteropServices.OSPlatform]::Linux) -or `
[System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform([System.Runtime.InteropServices.OSPlatform]::OSX)
}
}
$oses = $os_checks.GetEnumerator() | ForEach-Object {
$os_array = $_.Key
$os_check = $_.Value
$os_array | Where-Object {
$os_check.Invoke( $_ )
}
} | Where-Object { $_ }
$rids = if( $oses.Count ){
$global:version_ammendments = @(
@(
@(
"alpine", "centos", "debian", "fedora", "linuxmint",
"miraclelinux", "ol", "opensuse", "rhel", "rocky",
"sles", "tizen", "ubuntu"
),
{ param( $OS ); ($os_releases_file | Where-Object { $_ -match "VERSION_ID" }).Split("=")[1].Trim("`"' ") },
{ param( $OS, $Version ); "$OS.$Version" }
),
# Currently unsupported systems that do have version checks
# "android", "freebsd", "ios", "iossimulator"
# "maccatalyst", "omnios", "smartos", "solaris"
# "tvos", "tvossimulator"
@(
"osx",
{
param( $OS )
$v = $(sw_vers -productVersion)
$maj = $v.Split(".")[0]
$min = $v.Split(".")[1]
if( $maj -eq "10" ){
"$maj.$min"
} else {
$maj
}
},
{ param( $OS, $Version ); "$OS.$Version" }
),
@(
"win",
{
param( $OS )
$version = [System.Environment]::OSVersion.Version
switch -Wildcard ($version.ToString())
{
"10.0*" { "10" }
"6.3*" { "81" }
"6.2*" { "8" }
"6.1*" { "7" }
default { "" }
}
},
{ param( $OS, $Version ); "$($OS)$($Version)" }
)
)
$oses = $oses | ForEach-Object {
$os = $_
$version_ammendment = $version_ammendments | Where-Object { $_[0] -contains $os }
$ammended = if( $version_ammendment.Count ){
$version_ammendment[2].Invoke( $os, $version_ammendment[1].Invoke( $os ) )
} else {
$os
}
$ammended
}
$oses | ForEach-Object {
$os = $_
(@( $os, $arch.ToLower() ) | Where-Object { $_ }) -join "-"
}
} else {
(@( "any", $arch.ToLower() ) | Where-Object { $_ }) -join "-"
}
# aot checks are not required for PowerShell
# need to check the RID graph for the top most RID in the list of detected RIDs
# I think I will create a set of graphs for each RID, then check which RID only occurs in one set of graphs
$graphs = @{}
$rids | ForEach-Object {
$graphs[$_] = [System.Collections.ArrayList]::new()
$graphs[$_].add( [System.Collections.ArrayList]::new() ) | Out-Null
& $grapher $_ $graphs[$_][0] $graphs[$_]
}
$rid = $rids | ForEach-Object {
# iterate through the other graph sets and count how many of the sets contain the current RID
$output = @{
"rid" = $_
"tally" = 0
}
If( $graphs.Keys.Count -ne 1 ){
$graphs.Keys | Where-Object { $_ -ne $output.rid } | ForEach-Object {
$graph = $graphs[$_]
$graph | Where-Object { $_.Contains( $output.rid ) } | ForEach-Object { $output.tally += 1 }
}
}
$output
} | Where-Object { $_.tally -eq 0 } | ForEach-Object { $_.rid } | Select-Object -First 1
if( $rid.Count -ne 1 ){
Write-Host "Unable to determine the correct RID for this system."
Write-Host "Possible RID Count: $($rid.Count)"
Write-Host "Possible RIDs: $($rid -join ", ")"
Write-Host "All RIDs Detected: $($rids -join ", ")"
}
$Exported | Add-Member `
-MemberType NoteProperty `
-Name Runtime `
-Value $rid
$Exported
}
@anonhostpi
Copy link
Author

anonhostpi commented Oct 6, 2023

Optimization 1:

@anonhostpi
Copy link
Author

anonhostpi commented Oct 6, 2023

Improvement 2:

@anonhostpi
Copy link
Author

anonhostpi commented Oct 6, 2023

Improvement 3:

@anonhostpi
Copy link
Author

anonhostpi commented Oct 7, 2023

Improvement 4:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment