在C#中编写PowerShell模块时,如何存储模块状态?

时间:2016-09-30 20:31:04

标签: c# powershell

我正在用连接数据库的C#编写PowerShell模块。该模块具有Get-MyDatabaseRecord cmdlet,可用于查询数据库。如果变量PSCredential中有$MyCredentials个对象,则可以像这样调用cmdlet:

PS C:\> Get-MyDatabaseRecord -Credential $MyCredentials -Id 3


MyRecordId    : 3
MyRecordValue : test_value

问题是,每次调用Credential时都必须指定Get-MyDatabaseRecord参数是繁琐且低效的。如果你可以只调用一个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 Session State"谈论会话状态的事情。该页面显示"会话状态数据"包含"会话状态变量信息",但它没有详细说明这些信息是什么或如何访问它。

该页面还说the SessionState class可用于访问会话状态数据。此类包含名为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的输出中。我希望我的变量对用户隐藏。

3 个答案:

答案 0 :(得分:6)

一种方法是使用输出连接对象的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自动变量将您传入的凭据设置为我模块的任何导出命令的默认值。当然,你的逻辑可能不一样,但它可能会向你展示这种方法。

答案 1 :(得分:1)

我有类似的想法,但从来没有必要构建一个完整而干净的实现。存储似乎适合我的连接的状态的一种方法是将该状态封装在PSDrive中。这些驱动器已集成到会话状态中。

这方面的文档并不总是很明显,但它涉及编写一个派生自 $shape->getActiveParagraph()->getBulletStyle()->setBulletType(Bullet::TYPE_BULLET); $shape->createTextRun('A class library'); $shape->createParagraph()->createTextRun('Written in PHP'); $shape->createParagraph()->createTextRun('Representing a presentation'); $shape->createParagraph()->createTextRun('Supports writing to different file formats'); 的类,它具有对凭据管理的内置支持。我记得它,DriveCmdletProvider方法可以返回一个派生自NewDrive的自定义类,其中包含私有或内部成员,以存储您需要的内容。

然后,您可以使用PSDriveInfoNew-PSDrive建立/断开与驱动器名称的连接连接。 Remove-PSDrive提供的动态参数允许您根据具体情况自定义参数DriveCmdletProvider

有关详细信息,请参阅MSDN here

答案 2 :(得分:1)

我想最简单的方法是在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];
}