При написании модуля PowerShell на С#, как сохранить состояние модуля?

Я пишу модуль PowerShell на С#, который подключается к базе данных. Модуль имеет командлет Get-MyDatabaseRecord, который может использоваться для запроса базы данных. Если в переменной $MyCredentials есть объект PSCredential, вы можете вызвать командлет следующим образом:

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


MyRecordId    : 3
MyRecordValue : test_value

Проблема заключается в том, что каждый раз, когда вы вызываете Get-MyDatabaseRecord, нужно указать параметр Credential, который является утомительным и неэффективным. Было бы лучше, если бы вы могли просто вызвать один командлет для подключения к базе данных, а затем еще один, чтобы получить запись:

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


MyRecordId    : 3
MyRecordValue : test_value

Чтобы это было возможно, командлет Connect-MyDatabase должен хранить объект подключения к базе данных где-нибудь, чтобы командлет Get-MyDatabaseRecord мог получить этот объект. Как мне это сделать?

Идеи, о которых я думал

Использовать статическую переменную

Я мог бы просто определить статическую переменную где-нибудь, чтобы содержать соединение с базой данных:

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. Я хочу, чтобы моя переменная была скрыта от пользователя.

Ответ 1

Один подход заключается в использовании командлета или функции, которая выводит объект соединения. Этот объект может быть просто объектом PSCredential, или он может содержать учетные данные и другую информацию, такую ​​как строка соединения. Теперь вы сохраняете это в переменной, и вы можете продолжать это делать, но вы также можете использовать $PSDefaultParamterValues ​​для хранения этого значения и передать его всем соответствующим командлетам в модуле.

Я никогда не писал С# -модуль, но я сделал что-то подобное в 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. Конечно, ваша логика может быть не такой, но она может показать вам подход.

Ответ 2

У меня были подобные мысли, но у меня не было необходимости строить полную и чистую реализацию. Один из способов хранения состояния, который, как мне кажется, подходит для моих соединений, заключался в том, чтобы инкапсулировать это состояние в PSDrive. Такие диски интегрированы в состояние сеанса.

Документы не всегда очевидны в этом вопросе, но это связано с написанием класса, который происходит от DriveCmdletProvider, который имеет встроенную поддержку управления учетными данными. Как я помню, метод NewDrive может возвращать пользовательский класс, полученный из PSDriveInfo, с дополнительными членами, включая частные или внутренние члены, для хранения необходимого вам.

Затем вы можете использовать New-PSDrive и Remove-PSDrive для установления соединения/соединения соединений с именем диска. Динамические параметры, предоставленные DriveCmdletProvider, позволяют настраивать параметры для New-PSDrive для вашей специфики.

Подробнее см. MSDN здесь.

Ответ 3

Я полагаю, что самый простой способ - просто создать расширенную функцию в ps и использовать $ script: mycred

Но если вам нужно придерживаться чистого С# и поддерживать несколько пробелов, может быть возможно сделать словарь идентификаторов пробелов и токенов

public static class TokenCollection {
    public static readonly Dictionary<Guid,PSObject> Tokens = new Dictionary<Guid,PSObject>();
}

Затем вы добавляете текущую текущую рабочую область внутри вашего токена.

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