Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 297
RobinHu Avatar asked Dec 11 '13 08:12

RobinHu


People also ask

How do PowerShell cmdlets work?

Cmdlets perform an action and typically return a Microsoft . NET object to the next command in the pipeline. A cmdlet is a single command that participates in the pipeline semantics of PowerShell. This includes binary (C#) cmdlets, advanced script functions, CDXML, and Workflows.

What is the PowerShell command to list all cmdlets?

Description. The Get-Command cmdlet gets all commands that are installed on the computer, including cmdlets, aliases, functions, filters, scripts, and applications. Get-Command gets the commands from PowerShell modules and commands that were imported from other sessions.

Which is the syntax naming convention that the PowerShell cmdlets follow?

Use Pascal Case for Cmdlet Names (SD02) Use Pascal case for parameter names. In other words, capitalize the first letter of verb and all terms used in the noun.

Does PowerShell use cmdlets?

A PowerShell cmdlet is a lightweight command that is used in the Windows PowerShell environment. The Windows PowerShell runtime invokes these cmdlets within the context of automation scripts that are provided at the command line.


2 Answers

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.

like image 87
mjolinor Avatar answered Oct 26 '22 22:10

mjolinor


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.

like image 24
Steve Friedl Avatar answered Oct 26 '22 22:10

Steve Friedl