Skip to content
7 changes: 7 additions & 0 deletions .ci/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,13 @@ jobs:
"Authorization" = "Bearer $env:GITHUB_TOKEN"
}

# Secret variables are not exposed to fork PR builds. Use the public API without
# authentication in that case instead of sending an invalid Authorization header.
if ([string]::IsNullOrWhiteSpace($env:GITHUB_TOKEN) -or
$env:GITHUB_TOKEN -match '^\$\([^)]+\)$') {
$null = $headers.Remove("Authorization")
}

# Get releases
$releases = Invoke-RestMethod -Uri $api -Headers $headers -RetryIntervalSec 30 -MaximumRetryCount 10

Expand Down
62 changes: 62 additions & 0 deletions src/code/ArgumentCompleter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,68 @@
using System.Management.Automation;
using System.Management.Automation.Language;

internal class RuntimeIdentifierCompleter : IArgumentCompleter
{
private static readonly string[] s_knownRids = new[]
{
"win-x64", "win-x86", "win-arm64",
"linux-x64", "linux-arm64", "linux-arm", "linux-musl-x64", "linux-musl-arm64",
"osx-x64", "osx-arm64",
};

public IEnumerable<CompletionResult> CompleteArgument(
string commandName,
string parameterName,
string wordToComplete,
CommandAst commandAst,
IDictionary fakeBoundParameters)
{
wordToComplete = Utils.TrimQuotes(wordToComplete);
var wordToCompletePattern = WildcardPattern.Get(
pattern: string.IsNullOrWhiteSpace(wordToComplete) ? "*" : wordToComplete + "*",
options: WildcardOptions.IgnoreCase);

foreach (string rid in s_knownRids)
{
if (wordToCompletePattern.IsMatch(rid))
{
yield return new CompletionResult(rid);
}
}
}
}

internal class TargetFrameworkCompleter : IArgumentCompleter
{
private static readonly string[] s_knownTfms = new[]
{
"net472", "net48",
"netstandard2.0", "netstandard2.1",
"net6.0", "net7.0", "net8.0", "net9.0",
};

public IEnumerable<CompletionResult> CompleteArgument(
string commandName,
string parameterName,
string wordToComplete,
CommandAst commandAst,
IDictionary fakeBoundParameters)
{
wordToComplete = Utils.TrimQuotes(wordToComplete);
var wordToCompletePattern = WildcardPattern.Get(
pattern: string.IsNullOrWhiteSpace(wordToComplete) ? "*" : wordToComplete + "*",
options: WildcardOptions.IgnoreCase);

foreach (string tfm in s_knownTfms)
{
if (wordToCompletePattern.IsMatch(tfm))
{
yield return new CompletionResult(tfm);
}
}
}
}

internal class RepositoryNameCompleter : IArgumentCompleter
{
public IEnumerable<CompletionResult> CompleteArgument(
Expand Down
374 changes: 363 additions & 11 deletions src/code/InstallHelper.cs

Large diffs are not rendered by default.

26 changes: 25 additions & 1 deletion src/code/InstallPSResource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,28 @@ public string TemporaryPath
[Parameter]
public SwitchParameter AuthenticodeCheck { get; set; }

/// <summary>
/// Specifies the Runtime Identifier (RID) to filter platform-specific assets for.
/// When specified, only runtime assets matching this RID are installed instead of the auto-detected platform.
/// Use this for cross-platform deployment scenarios (e.g., preparing a Linux package from Windows).
/// Valid values follow the .NET RID catalog: win-x64, linux-x64, osx-arm64, etc.
/// </summary>
[Parameter]
[ValidateNotNullOrEmpty]
[ArgumentCompleter(typeof(RuntimeIdentifierCompleter))]
public string RuntimeIdentifier { get; set; }

/// <summary>
/// Specifies the Target Framework Moniker (TFM) to select for lib/ folder filtering.
/// When specified, only lib/ assets matching this TFM are installed instead of the auto-detected framework.
/// Use this for cross-platform deployment scenarios (e.g., preparing a .NET 6 package from a .NET 8 host).
/// Valid values follow NuGet TFM format: net472, netstandard2.0, net6.0, net8.0, etc.
/// </summary>
[Parameter]
[ValidateNotNullOrEmpty]
[ArgumentCompleter(typeof(TargetFrameworkCompleter))]
public string TargetFramework { get; set; }

/// <summary>
/// Passes the resource installed to the console.
/// </summary>
Expand Down Expand Up @@ -597,7 +619,9 @@ private void ProcessInstallHelper(string[] pkgNames, string pkgVersion, bool pkg
pathsToInstallPkg: _pathsToInstallPkg,
scope: scope,
tmpPath: _tmpPath,
pkgsInstalled: _packagesOnMachine);
pkgsInstalled: _packagesOnMachine,
runtimeIdentifier: RuntimeIdentifier,
targetFramework: TargetFramework);

if (PassThru)
{
Expand Down
100 changes: 100 additions & 0 deletions src/code/InternalHooks.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Reflection;
using System.Runtime.InteropServices;

namespace Microsoft.PowerShell.PSResourceGet.UtilClasses
{
Expand Down Expand Up @@ -43,5 +47,101 @@ public static string GetUserString()
{
return Microsoft.PowerShell.PSResourceGet.Cmdlets.UserAgentInfo.UserAgentString();
}

#region RuntimeIdentifierHelper Test Hooks

/// <summary>
/// Returns the detected RID for the current platform.
/// </summary>
public static string GetCurrentRuntimeIdentifier()
{
return RuntimeIdentifierHelper.GetCurrentRuntimeIdentifier();
}

/// <summary>
/// Returns the compatible RID list for the current platform.
/// </summary>
public static IReadOnlyList<string> GetCompatibleRuntimeIdentifiers()
{
return RuntimeIdentifierHelper.GetCompatibleRuntimeIdentifiers();
}

/// <summary>
/// Returns the compatible RID list for a specified RID.
/// </summary>
public static IReadOnlyList<string> GetCompatibleRuntimeIdentifiersFor(string rid)
{
return RuntimeIdentifierHelper.GetCompatibleRuntimeIdentifiers(rid);
}

/// <summary>
/// Checks if a given RID is compatible with the current platform.
/// </summary>
public static bool IsCompatibleRid(string rid)
{
return RuntimeIdentifierHelper.IsCompatibleRid(rid);
}

#endregion

#region RuntimePackageHelper Test Hooks

/// <summary>
/// Checks if a folder name looks like a .NET Runtime Identifier.
/// </summary>
public static bool IsRidFolder(string folderName)
{
return RuntimePackageHelper.IsRidFolder(folderName);
}

/// <summary>
/// Checks if a zip entry path contains runtime-specific assets.
/// </summary>
public static bool IsRuntimesEntry(string entryFullName)
{
return RuntimePackageHelper.IsRuntimesEntry(entryFullName);
}

/// <summary>
/// Extracts the RID from a runtimes entry path.
/// </summary>
public static string GetRidFromRuntimesEntry(string entryFullName)
{
return RuntimePackageHelper.GetRidFromRuntimesEntry(entryFullName);
}

/// <summary>
/// Determines if a zip entry should be included for the current platform.
/// </summary>
public static bool ShouldIncludeEntry(string entryFullName)
{
return RuntimePackageHelper.ShouldIncludeEntry(entryFullName);
}

/// <summary>
/// Determines if a zip entry should be included for an explicit target RID.
/// </summary>
public static bool ShouldIncludeEntryForRid(string entryFullName, string targetRid)
{
return RuntimePackageHelper.ShouldIncludeEntry(entryFullName, targetRid);
}

/// <summary>
/// Checks if a given RID is compatible with a specified target RID.
/// </summary>
public static bool IsCompatibleRidWith(string candidateRid, string targetRid)
{
return RuntimeIdentifierHelper.IsCompatibleRid(candidateRid, targetRid);
}

/// <summary>
/// Returns a list of RIDs from a zip file's runtimes folder.
/// </summary>
public static IReadOnlyList<string> GetAvailableRidsFromZipFile(string zipPath)
{
return RuntimePackageHelper.GetAvailableRidsFromZipFile(zipPath);
}

#endregion
}
}
Loading
Loading