简体   繁体   中英

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.

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. 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.

What is best practices for keeping state between calls to different Cmdlets?

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.

If they're running V3 or better, you could have the setup set those values in $PSDefaultParameterValues.

See:

get-help about_parameters_default_values

for details on setting values.

Maybe something like powershell's CimSession support? You use new-cimsession to create a session (which contains state) and then pass the cimsession object to various other cmdlets. This wouldn't work with get-item as mentioned in the OP though.

However the EDIT in the OP describes an implementation that would be unlikely to work with get-item (if I understand correctly).

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. PS Providers will work with cmdlets like get-item and can hold state via PSDrives (as in get-PSDrive).

You're looking for 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. 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.

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(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(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. If the username is still missing after this, it fails (probably by ThrowTerminatingError ).

Your Select-Project could work the same way.

I usually provide a Get-MyState cmdlet that just writes the state object to the pipeline output, mainly for debugging. You're not limited to just one variable if your application warrants separating these things.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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