Reputation: 3398
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.
Upvotes: 13
Views: 2296
Reputation: 362
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.
Upvotes: 6
Reputation: 77
I suppose the easiest way is to just make an advanced function in ps and use $script:mycred
But if u need to stick to pure c# and support multiple runspaces, it may be possible to make a dictionary of runspace ids and tokens
public static class TokenCollection {
public static readonly Dictionary<Guid,PSObject> Tokens = new Dictionary<Guid,PSObject>();
}
You then add your current runspace guid inside with your token
Guid runspId = Guid.Empty;
using (var runsp = PowerShell.Create(RunspaceMode.CurrentRunspace))
{
runspId = runsp.Runspace.InstanceId;
TokenCollection.Add(runspId, token);
}
Get a token like this
PSObject token = null;
if (TokenCollection.Tokens.ContainsKey(runspaceId))
{
token = TokenCollection.Tokens[runspId];
}
Upvotes: 1
Reputation: 6874
I've had similar thoughts, but never had the need to build a full and clean implementation. One approach to storing state that seemed appropriate for my connections was to encapsulate that state in a PSDrive. The such drives are integrated into session state.
The docs aren't always obvious on this, but it involves writing a class that derives from DriveCmdletProvider
, which has built-in support for credential management. As I remember it, the NewDrive
method can return a custom class derived from PSDriveInfo
with additional members, including private or internal members, to store what you need.
You can then use New-PSDrive
and Remove-PSDrive
to establish/break connections connection with a drive name. Dynamic parameters provided by the DriveCmdletProvider
let you customize parameters to New-PSDrive
for your specifics.
See MSDN here for details.
Upvotes: 1