在 C# 中编写 PowerShell 模块时,如何存储模块状态
本文关键字:存储模块 状态 模块 PowerShell | 更新日期: 2023-09-27 18:09:39
我正在用C#编写一个连接到数据库的PowerShell模块。该模块具有可用于查询数据库的Get-MyDatabaseRecord
cmdlet。如果变量 $MyCredentials
中有 PSCredential
对象,则可以按如下所示调用 cmdlet:
PS C:'> Get-MyDatabaseRecord -Credential $MyCredentials -Id 3
MyRecordId : 3
MyRecordValue : test_value
问题是,每次调用Get-MyDatabaseRecord
时都必须指定Credential
参数既乏味又低效。如果只调用一个 cmdlet 连接到数据库,然后调用另一个 cmdlet 获取记录,那就更好了:
PS C:'> Connect-MyDatabase -Credential $MyCredentials
PS C:'> Get-MyDatabaseRecord -Id 3
MyRecordId : 3
MyRecordValue : test_value
为了实现这一点,Connect-MyDatabase
cmdlet 必须将数据库连接对象存储在某个位置,以便Get-MyDatabaseRecord
cmdlet 可以获取该对象。我应该怎么做?
我想到的想法
使用静态变量
我可以在某处定义一个静态变量来包含数据库连接:
static class ModuleState
{
internal static IDbConnection CurrentConnection { get; set; }
}
但是,全局可变状态通常是一个坏主意。这会以某种方式引起问题,还是这是一个很好的解决方案?
(问题的一个示例是,如果多个 PowerShell 会话以某种方式共享程序集的同一实例。然后,所有会话都会无意中共享一个CurrentConnection
属性。但我不知道这是否真的可能。
使用 PowerShell 模块会话状态
MSDN 页面"Windows PowerShell 会话状态"讨论了称为会话状态的东西。该页面说"会话状态数据"包含"会话状态变量信息",但没有详细说明这些信息是什么或如何访问它。
该页面还说SessionState
类可用于访问会话状态数据。此类包含一个名为 PSVariable
的属性,类型为 PSVariableIntrinsics
。
但是,我对此有两个问题。第一个问题是访问 SessionState
属性需要我从 PSCmdlet
继承而不是Cmdlet
,我不确定我是否要这样做。
第二个问题是我不知道如何将变量设为私有。这是我正在尝试的代码:
const int TestVariableDefault = 10;
const string TestVariableName = "TestVariable";
int TestVariable
{
get
{
return (int)SessionState.PSVariable.GetValue(TestVariableName,
TestVariableDefault);
}
set
{
PSVariable testVariable = new PSVariable(TestVariableName, value,
ScopedItemOptions.Private);
SessionState.PSVariable.Set(testVariable);
}
}
TestVariable
属性就像我期望的那样。但是尽管我使用的是ScopedItemOptions.Private
,我仍然可以通过输入$TestVariable
在提示符下访问此变量,并且该变量列在Get-Variable
的输出中。我希望我的变量对用户隐藏。
一种方法是使用输出连接对象的 cmdlet 或函数。此对象可以只是 PSCredential 对象,也可以包含凭据和其他信息(如连接字符串(。现在将其保存在变量中,可以继续执行此操作,但也可以使用$PSDefaultParamterValues来存储此值并将其传递给模块中的所有相应 cmdlet。
我从未编写过 C# 模块,但我在 PS 中做了类似的事情:
function Set-DefaultCredential
{
param
(
[PSCredential]
$Credential
)
$ModuleName = (Get-Item -Path $PSScriptRoot).Parent.Name
$Module = Get-Module -Name $ModuleName
$Commands = $Module.ExportedCommands.GetEnumerator() | Select-Object -ExpandProperty value | Select-Object -ExpandProperty name
foreach ($Command in $Commands)
{
$Global:PSDefaultParameterValues["$Command`:Credential"] = $Credential
}
}
此代码使用 $PSDefaultParameterValues auto 变量将你传入的凭据设置为我的模块的任何导出命令的默认凭据。当然,你的逻辑可能不一样,但它可能会告诉你方法。
我也有类似的想法,但从来没有需要构建一个完整而干净的实现。 存储状态的一种似乎适合我的连接的方法是将该状态封装在 PSDrive 中。 此类驱动器已集成到会话状态中。
文档在这方面并不总是很明显,但它涉及编写一个派生自 DriveCmdletProvider
的类,该类具有对凭据管理的内置支持。 我记得,NewDrive
方法可以返回从具有其他成员(包括私有或内部成员(的PSDriveInfo
派生的自定义类,以存储所需的内容。
然后,您可以使用New-PSDrive
和Remove-PSDrive
建立/断开具有驱动器名称的连接连接。 DriveCmdletProvider
提供的动态参数允许您自定义参数以New-PSDrive
您的具体情况。
有关详细信息,请参阅此处的 MSDN。
我想最简单的方法是在ps中创建一个高级函数并使用$script:mycred
但是,如果您需要坚持使用纯 c# 并支持多个运行空间,则可以创建运行空间 ID 和标记的字典
public static class TokenCollection {
public static readonly Dictionary<Guid,PSObject> Tokens = new Dictionary<Guid,PSObject>();
}
然后,使用令牌将当前运行空间 guid 添加到其中
Guid runspId = Guid.Empty;
using (var runsp = PowerShell.Create(RunspaceMode.CurrentRunspace))
{
runspId = runsp.Runspace.InstanceId;
TokenCollection.Add(runspId, token);
}
获取这样的令牌
PSObject token = null;
if (TokenCollection.Tokens.ContainsKey(runspaceId))
{
token = TokenCollection.Tokens[runspId];
}