При написании модуля 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];
}