[英]Run Az PowerShell modules in C# .NET PowerShell environment fails to load Az modules
我试图在 C# 中运行 AZ PowerShell 模块,但失败了。
我的理解是:
我需要 PowerShellGet 来下载和安装模块。
PowerShellGet 版本 3 有一个名为Install-PSResource
的命令,它结合了 PowerShellGet v2 中的Install-Module
和Install-Script
cmdlet 。
所以我从 NuGet 库中手动下载了 PowerShellGet 并尝试对其进行Import-Module
因为 PowerShell 环境是从 C# 创建的,所以我创建了一个随机名称的目录来保存 PowerShellGet 从互联网下载的模块,然后将环境变量PSModulePath
设置到该文件夹以避免污染文件系统。
现在我可以使用像Install-PSResource -Name Az -Repository PSGallery -TrustRepository -Scope CurrentUser -Quiet;
下载并安装 Azure PowerShell 模块
问题:加载 Az 模块失败,因为由于某种原因找不到它。
错误:
error: The specified module 'Az' was not loaded because no valid module file was found in any module directory.
error: The specified module 'Az.Accounts' was not loaded because no valid module file was found in any module directory.
error: The specified module 'Az.RecoveryServices' was not loaded because no valid module file was found in any module directory.
error: The specified module 'Az.Storage' was not loaded because no valid module file was found in any module directory.
error: The term 'New-AzStorageAccount' is not recognized as a name of a cmdlet, function, script file, or executable program.
Check the spelling of the name, or if a path was included, verify that the path is correct and try again.
代码:
/// <summary>
/// This utility class uses Microsoft.PowerShell.Sdk to simplify running PowerShell scripts
/// modules that install modules from NuGet.
/// </summary>
public static class PowerShellSdkUtility
{
private const string PowerShellGet = "PowerShellGet";
private const string Nupkg = "nupkg";
private const string PackageGetUrlTemplate = "https://psg-prod-eastus.azureedge.net/packages/{0}.{1}.{2}";
private static readonly string[] AzureModules = {
"Az",
"Az.Accounts",
"Az.RecoveryServices",
"Az.Storage",
};
public readonly record struct PowerShellResponse(
List<string> Result,
List<string> ErrorMessages,
bool Failed);
private readonly record struct PowerShellPackage(
string Name,
string ImportVersion,
string FullVersion);
/// <summary>
/// Runs a PowerShell script string
/// </summary>
/// <param name="script">PowerShell script to run</param>
/// <param name="environments">Environment variables accessible to PowerShell</param>
/// <returns>PowerShell invocation result</returns>
public static async Task<PowerShellResponse> RunPsScript(string script, Dictionary<string, string>? environments = null)
{
return await RunPsSession(powershell =>
{
powershell.AddScript(script);
}, environments);
}
/// <summary>
/// Runs a PowerShell file given a path
/// </summary>
/// <param name="path">PowerShell script path to run</param>
/// <param name="environments">Environment variables accessible to PowerShell</param>
/// <returns>PowerShell invocation result</returns>
public static async Task<PowerShellResponse> RunPsFile(string path, Dictionary<string, string>? environments = null)
{
var workingDirectory = Path.GetDirectoryName(path)!;
return await RunPsSession(powershell =>
{
powershell.AddScript($"Set-Location -Path {workingDirectory};");
powershell.AddScript(File.ReadAllText(path));
}, environments);
}
/// <summary>
/// Given an array of packages:
/// 1) it downloads the package nupkg from PowerShell gallery
/// 2) unzip nupkg file to access modules folder
/// 3) Import-Module package
/// </summary>
/// <param name="folderPrefix">Folder prefix to put the packages into</param>
/// <param name="powerShell">PowerShell instance</param>
/// <param name="packages">List of NuGet packages</param>
private static async Task DownloadNugetPackages(string folderPrefix, PowerShell powerShell, params PowerShellPackage[] packages)
{
using var client = new HttpClient();
foreach (var package in packages)
{
var nupkgPackagePath = Path.Combine(folderPrefix, $"{package.Name}.{Nupkg}");
var nupkgPackageGetModulePath = Path.Combine(folderPrefix, package.Name);
await client.DownloadFileTaskAsync(
new Uri(string.Format(PackageGetUrlTemplate, package.Name, package.FullVersion, Nupkg).ToLower()),
nupkgPackagePath);
// UnZip Nupkg file
ZipFile.ExtractToDirectory(
nupkgPackagePath,
nupkgPackageGetModulePath);
powerShell.AddScript($"Import-Module {nupkgPackageGetModulePath} -RequiredVersion {package.ImportVersion} -Force;");
}
}
/// <summary>
/// This function runs a PowerShell script inside C#.
/// </summary>
/// <param name="environments">Environment variables accessible to PowerShell</param>
/// <param name="action">Action to run against PowerShell instance</param>
/// <returns></returns>
private static async Task<PowerShellResponse> RunPsSession(Action<PowerShell> action, Dictionary<string, string>? environments)
{
var sessionId = Guid.NewGuid().ToString();
var folderPrefix = Path.Combine(Directory.GetCurrentDirectory(), sessionId);
var packages = new[]
{
new PowerShellPackage(PowerShellGet, "3.0.17", "3.0.17-beta17")
};
var errorMessages = new List<string>();
// Setting up the PowerShell runspace
var initialSessionState = InitialSessionState.CreateDefault();
initialSessionState.ExecutionPolicy = ExecutionPolicy.Unrestricted;
initialSessionState.LanguageMode = PSLanguageMode.FullLanguage;
environments ??= new Dictionary<string, string>();
// This environment variable instructs PowerShellGet to download modules into current session folder
// to avoid polluting the namespace and file system.
environments.Add("PSModulePath", folderPrefix);
// Add environment variables
foreach (var (key, value) in environments)
{
initialSessionState.EnvironmentVariables.Add(
new SessionStateVariableEntry(
key,
value,
$"{folderPrefix}/{key}={value}"));
}
PowerShell? powershell = null;
try
{
// Create session folder
Directory.CreateDirectory(folderPrefix);
powershell = PowerShell.Create(initialSessionState);
powershell.AddScript("Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy Unrestricted;");
// Manually download and import the packages. This is needed because PowerShell that comes with C# is
// bare bone and does not have a way to interact with NuGet.
await DownloadNugetPackages(folderPrefix, powershell, packages);
// Install Az using the new Install-PSResource that comes with PowerShellGet v3+.
// It combines the legacy functions of the Install-Module and Install-Script cmdlets from PowerShellGet v2.
// Source: https://github.com/PowerShell/PowerShellGet/blob/master/help/Install-PSResource.md
foreach (var azureModule in AzureModules)
{
powershell.AddScript($"Install-PSResource -Name {azureModule} -Repository PSGallery -TrustRepository -Scope CurrentUser -Quiet;");
}
powershell.AddScript("Write-Output $env:PSModulePath");
action(powershell);
var result = powershell.Invoke();
if (powershell.HadErrors)
{
errorMessages.AddRange(powershell.Streams.Error.Select(x => x.ToString()));
}
return new PowerShellResponse(result.Select(x => x.ToString()).ToList(), errorMessages, powershell.HadErrors);
}
catch (Exception e)
{
if (powershell != null)
{
var info = powershell.Streams.Information.Select(x => x.ToString()).ToList();
var verbose = powershell.Streams.Verbose.Select(x => x.ToString()).ToList();
var warnings = powershell.Streams.Warning.Select(x => x.ToString()).ToList();
var progress = powershell.Streams.Progress.Select(x => x.ToString()).ToList();
var debugs = powershell.Streams.Debug.Select(x => x.ToString()).ToList();
}
return new PowerShellResponse(
new List<string>(),
new List<string>
{
$"Error occurred while running command: {e.Message}"
}.Concat(powershell?.Streams.Error.Select(x => x.ToString()) ?? Array.Empty<string>()).ToList(),
true);
}
finally
{
powershell?.Dispose();
DeleteDirectorySafely(folderPrefix);
}
}
private static void DeleteDirectorySafely(string path)
{
try
{
Directory.Delete(path, true);
}
catch (UnauthorizedAccessException e)
{
Console.Error.WriteLine($"UnauthorizedAccessException when deleting the directory {path}: {e.Message}");
}
catch (Exception e)
{
Console.Error.WriteLine($"Exception when deleting the directory {path}: {e.Message}");
}
}
}
class Program
{
public static async Task Main(string[] args)
{
//var (results, errors, _) = await PowerShellSdkUtility.RunPsFile(@"C:\Users\shesamian\RiderProjects\AzureStack-Fiji-Workloads\src\ASR-EdgeZone\E2EEZtoAZ-remote-cache.ps1",
var (results, errors, _) = await PowerShellSdkUtility.RunPsScript(@"
# Import Azure module
Import-Module 'Az'
Import-Module 'Az.Accounts'
Import-Module 'Az.RecoveryServices'
Import-Module 'Az.Storage'
New-AzStorageAccount",
new Dictionary<string, string>());
foreach (var errorMsg in errors)
{
await Console.Error.WriteLineAsync($"error: {errorMsg}");
}
foreach (var result in results)
{
Console.WriteLine($"result: {result}");
}
}
}
目标是在 csharp 中运行 azure PowerShell 模块
我已经在我的环境中复制并得到如下预期结果:
我使用了您的应用程序并从 GitHub 克隆并将您的Program.cs
更改为以下代码:
using System.Diagnostics;
using System.Management.Automation.Runspaces;
using System.Management.Automation;
var initialState = InitialSessionState.CreateDefault2();
initialState.ExecutionPolicy = Microsoft.PowerShell.ExecutionPolicy.Unrestricted;
using var ps = PowerShell.Create(initialState);
var results = ps.AddScript(@"
Install-PackageProvider -Name Nuget -Scope CurrentUser –Force
Install-Module –Name PowerShellGet -Scope CurrentUser –Force
Install-Module -Name Az -Scope CurrentUser -Repository PSGallery -Force
Install-Module -Name Az.Resources -AllowClobber -Scope CurrentUser
# Import Azure module
Import-Module 'Az'
Import-Module 'Az.Accounts'
Import-Module 'Az.RecoveryServices'
try {
Connect-AzAccount
Select-AzSubscription -SubscriptionName 'Azure XX'
New-AzResourceGroup -Name 'RGforYOU' -Location 'East US'
}
catch {
Write-Output 'badluck'
}
").Invoke();
Azure XX 是订阅的名称。
Connect-AzAccount
登录了 azure 帐户。 然后您将被重定向到 azure 登录页面,您可以在那里登录。 然后我使用Select-AzSubscription
命令设置需要在其中创建资源组的订阅
然后我使用New-AzResourceGroup
创建了一个资源组
当我在 Visual Studio 中运行时,没有出现如下错误:
然后它花了一些时间来运行,然后我在 Azure 门户中打开了资源组是否创建,它的创建如下:
代码参考取自:
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.