Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

When writing a PowerShell module in C#, how do I store module state?

Tags:

c#

powershell

I'm writing a PowerShell module in C# that connects to a database. The module has a Get-MyDatabaseRecord cmdlet which can be used to query the database. If you have a PSCredential object in the variable $MyCredentials, you can call the cmdlet like so:

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


MyRecordId    : 3
MyRecordValue : test_value

The problem is, having to specify the Credential parameter every time that you call Get-MyDatabaseRecord is tedious and inefficient. It would be better if you could just call one cmdlet to connect to the database, and then another to get the record:

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


MyRecordId    : 3
MyRecordValue : test_value

In order for that to be possible, the Connect-MyDatabase cmdlet has to store the database connection object somewhere so that the Get-MyDatabaseRecord cmdlet can obtain that object. How should I do this?

Ideas I've thought of

Use a static variable

I could just define a static variable somewhere to contain the database connection:

static class ModuleState
{
    internal static IDbConnection CurrentConnection { get; set; }
}

However, global mutable state is usually a bad idea. Could this cause problems somehow, or is this a good solution?

(One example of a problem would be if multiple PowerShell sessions somehow shared the same instance of my assembly. Then all of the sessions would inadvertently share a single CurrentConnection property. But I don't know if this is actually possible.)

Use PowerShell module session state

The MSDN page "Windows PowerShell Session State" talks about something called session state. The page says that "session-state data" contains "session-state variable information", but it doesn't go into detail about what this information is or how to access it.

The page also says that the SessionState class can be used to access session-state data. This class contains a property called PSVariable, of type PSVariableIntrinsics.

However, I have two problems with this. The first problem is that accessing the SessionState property requires me to inherit from PSCmdlet instead of Cmdlet, and I'm not sure if I want to do that.

The second problem is that I can't figure out how to make the variable private. Here's the code that I'm trying:

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);
    }
}

The TestVariable property works just as I would expect. But despite the fact that I'm using ScopedItemOptions.Private, I can still access this variable at the prompt by typing in $TestVariable, and the variable is listed in the output of Get-Variable. I want my variable to be hidden from the user.

like image 451
Tanner Swett Avatar asked Sep 30 '16 20:09

Tanner Swett


People also ask

Is PowerShell written in C#?

Although PowerShell Cmdlets are usually written in PowerShell, there are occasions when the level of integration with existing C# or VB libraries is awkward to achieve with PowerShell.

Does C# work in PowerShell?

From PowerShell, you use C# to expand what you can do in PowerShell. You can create cmdlts and providers to enable others to access application data. Or you can just create objects that can be used within a PowerShell script.


1 Answers

One approach would be to use a cmdlet or function that outputs a connection object. This object could be simply the PSCredential object, or it could contain the credential and other information like a connection string. You're saving this in a variable now and you can continue to do this, but you can also use $PSDefaultParamterValues to store this value and pass it to all the appropriate cmdlets in the module.

I've never written a C# module but I've done something similar in 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
    }
}

This code sets the credential you've passed in as the default for any of the exported commands of my module using the $PSDefaultParameterValues automatic variable. Of course your logic may not be the same but it might show you the approach.

like image 95
Matt McNabb Avatar answered Oct 11 '22 15:10

Matt McNabb