简体   繁体   English

在调用Powershell Cmdlet之间保持状态的最佳实践是什么?

[英]What is the best practice for keeping state between calls to Powershell Cmdlets?

I am a complete newbie to Powershell development and I am trying to write a Powershell Cmdlet in C# that will be used as an interface to a REST API. 我是Powershell开发的新手,我正在尝试用C#编写一个Powershell Cmdlet,它将用作REST API的接口。

I would like to have some kind of setup Cmdlet where the user will be prompted for an Uri, username and password to the REST API and then call Cmldlets like Get-Item without having to enter those parameters. 我希望有一些设置Cmdlet,用户将被提示输入REST API的Uri,用户名和密码,然后调用Cmldlets,如Get-Item,而不必输入这些参数。 Pretty much like the Azure provider for Powershell works where you can set the current subscription by calling Select-AzureSubscription and then call Save-AzureVhd without having to enter the subscription name again. 非常类似于Powershell的Azure提供程序,您可以通过调用Select-AzureSubscription设置当前订阅,然后再调用Save-AzureVhd而无需再输入订阅名称。

What is best practices for keeping state between calls to different Cmdlets? 在不同Cmdlet的调用之间保持状态的最佳实践是什么?

EDIT: I am not sure that this is the best way to solve it but what I did is that i added a singleton class that holds the state. 编辑:我不确定这是解决它的最佳方法,但我所做的是我添加了一个保持状态的单例类。 I have one Cmdlet, Select-Project -Name MyProject that sets a public property in my singleton class and then my other Cmdlets can access that property. 我有一个Cmdlet,Select-Project -Name MyProject在我的单例类中设置公共属性,然后我的其他Cmdlet可以访问该属性。

If they're running V3 or better, you could have the setup set those values in $PSDefaultParameterValues. 如果它们运行V3或更高版本,您可以让设置在$ PSDefaultParameterValues中设置这些值。

See: 看到:

get-help about_parameters_default_values

for details on setting values. 有关设置值的详细信息。

Maybe something like powershell's CimSession support? 也许像PowerShell的CimSession支持? You use new-cimsession to create a session (which contains state) and then pass the cimsession object to various other cmdlets. 您使用new-cimsession创建会话(包含状态),然后将cimsession对象传递给其他各种cmdlet。 This wouldn't work with get-item as mentioned in the OP though. 这不适用于OP中提到的get-item。

However the EDIT in the OP describes an implementation that would be unlikely to work with get-item (if I understand correctly). 然而,OP中的EDIT描述了一个不太可能与get-item一起使用的实现(如果我理解正确的话)。

If support by get-item was really a requirement then I believe a PS provider (as in get-PSProvider) would be the way to go. 如果get-item的支持确实是一个要求,那么我相信PS提供商(如在get-PSProvider中)将是可行的方式。 PS Providers will work with cmdlets like get-item and can hold state via PSDrives (as in get-PSDrive). PS提供商将使用像get-item这样的cmdlet,并且可以通过PSDrives保存状态(如在get-PSDrive中)。

You're looking for PSCmdlet.SessionState . 您正在寻找PSCmdlet.SessionState

I solved this same problem by creating a small bridge class, MyCmdlet that all my own cmdlets derive from, and it contains helpers that manage the session state, along with the definition of the object that holds the things you want to persist. 我通过创建一个我自己的cmdlet派生的小桥类MyCmdlet解决了同样的问题,它包含管理会话状态的帮助程序,以及包含要保留的内容的对象的定义。 In this case I'll just make up some simple things like a username and a database name. 在这种情况下,我只会编写一些简单的东西,比如用户名和数据库名。

// In MyCmdlet.cs

public class MyStateInfo {
  public string Username { get; set;}
  public string DbName { get; set;}
}

protected abstract class MyCmdlet : PSCmdlet {
    private const string StateName = "_Blah";

    protected MyStateInfo getState()
    {
        if ( ! SessionState.PSVariable.GetValue(StateName, null) is MyStateInfo s))
            SessionState.PSVariable.Set(StateName, s = new MyStateInfo());

        return s;
    }
}

At this point all your cmdlets should inherit from MyCmdlet , and getState() will always return an existing or new variable: changes to the class persist in that same session. 此时,所有cmdlet都应从MyCmdlet继承,并且getState()将始终返回现有或新变量:对该类的更改将在该同一会话中保留。

There are probably lots of ways to integrate this in your overall cmdlet parameter design, and this is still kinda new to me so I'm not sure if it's a best practice, but I've solved it by creating a helper cmdlet to set the initial values: 可能有很多方法可以将它集成到您​​的整体cmdlet参数设计中,这对我来说仍然是一个新的,所以我不确定它是否是最佳实践,但我通过创建一个帮助cmdlet来设置初始值:

[Cmdlet(VerbsCommon.Set, "MyUsername")]
public class Set_MyUsername : MyCmdlet {
  [Parameter(Mandatory = true, Position = 1)] 
  public string Username {get; set; }

  protected override void ProcessRecord()
  {
       base.ProcessRecord();

       WriteVerbose($"Saving {Username} in session state");
       getState().Username = Username;
  }
}

Then some later cmdlet needs to do something with that: 然后一些后来的cmdlet需要做一些事情:

[Cmdlet(VerbsCommunication.Connect, "Database")]
public class Connect_Database : MyCmdlet {
    [Parameter(Mandatory = false)]
    public string Username { get; set; }

    // other parameters here

    protected override void BeginProcessing()
    {
        base.BeginProcessing();

        if (Username == null)
            Username = getState().Username;

        if (Username == null)
        { // ERROR HERE: missing username }
    }
    // more stuff
}

Then, your Connect-Database will take an explicit -Username steve parameter (not consulting or touching session state), but without that parameter it pulls it from your session state object. 然后,您的Connect-Database将采用显式的-Username steve参数(不咨询或触摸会话状态),但如果没有该参数,它会从会话状态对象中提取它。 If the username is still missing after this, it fails (probably by ThrowTerminatingError ). 如果此后用户名仍然缺失,则失败(可能是ThrowTerminatingError )。

Your Select-Project could work the same way. 您的Select-Project可以以相同的方式工作。

I usually provide a Get-MyState cmdlet that just writes the state object to the pipeline output, mainly for debugging. 我通常提供一个Get-MyState cmdlet, Get-MyState将状态对象写入管道输出,主要用于调试。 You're not limited to just one variable if your application warrants separating these things. 如果您的应用程序保证将这些内容分开,您不仅限于一个变量。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM