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?
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.)
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.
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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With