Weekend Scripter: Wow! I Didn’t Know You Could Do That in PowerShell!

Weekend Scripter: Wow! I Didn’t Know You Could Do That in PowerShell!

  • Comments 11
  • Likes

Summary: Discover the power of the “double splat.”

Honorary Scripting Guy, Sean Kearney, here filling in for our good friend, Ed. He’s keeping himself inside where it’s warm. I’m here in Canada typing really fast to keep even warmer! It’s 31 degrees below Celsius here in Ottawa, don’t cha know?

I was developing a solution that needed to authenticate against two Active Directory domains, and the client threw in a twist during development (yes, these things happen). During this process, I discovered a cool trick called “double splatting.”

In my script, I first needed to authenticate against two possible Active Directory domains. “Well, that’s awfully easy,“ I thought to myself. “I’ll simply have two Windows PowerShell variables to hold the credentials and the domain controller, and use them as I create users. Too easy.”

Yes, that was the original thought. But as the script grew, feature creep from the client came forth. Additional parameters, such as target organizational units were be added to the mix.  Multiple situations with more Active Directory cmdlets appeared.         

I found that I was adding the same information over and over and over…

So to resolve that issue, I decided to bite the bullet and use “splatting.” Splatting is a cool technique in Windows PowerShell where you can define parameters for a cmdlet and its values within an array, and pass that object directly to the cmdlet. The big advantage of splatting (other than avoiding some typing and a readability improvement) is that if there are common parameters that I need to change “en-masse” in a script, I can edit the array.

In my original script, I wrote something like this to query a user:

#Domain Controller

$DC=”192.168.1.5”

 

#Domain Credentials

$MyDomain=’Contoso.Dev’

$MyClearTextUsername=’ServiceAdminAcct’

$MyClearTextPassword=’S3cr3tP@ssw0rd!’

 

$MyUsernameDomain=$MyDomain+’\’+$MyClearTextUsername

 

$SecurePassword=Convertto-SecureString –String

$MyClearTextPassword –AsPlainText –force

 

$Cred=New-object System.Management.Automation.PSCredential $MyUsernameDomain,$SecurePassword

Then each time I would try to query a user, I would do this:

REMOVE-ADUser JohnSmith –credential $Cred –server $DC

To use splatting, first create an array that contains the names of parameters for a cmdlet and their values:

$ADparams=@{‘credential’=$Cred;’server’=$DC}

Then pass it directly to the cmdlet:

REMOVE-ADUser JohnSmith @ADparams

This now makes flipping the credentials and servers easier. I can also add additional parameters to the same array, and the cmdlets will use them. I was in my glory. Life was good…until…

The client decided, yes, feature creeeeep!

They needed to ensure that they could have a safety switch to test the script—not just a single cmdlet—without hurting data.

Well, in Windows PowerShell, that’s easy. That’s the –whatif parameter. I can flip cmdlets to automatically default to a whatif mode by setting this variable:

$WhatIfPreference=$TRUE

This worked like a charm, but every cmdlet (including those for creating logs) warned about what they were going to do without doing it. In my case, I only wanted it to happen for Active Directory cmdlets.

Soooooo…scrap that idea.  

Then I stumbled across something really cool. When mucking about, I found that I can splat twice!

Yes, the parser doesn’t only work on one splat, you can have multiple in the same cmdlet. In my case, I created a tiny splat for the –whatif parameter by defining one of two possible values.

$Safety=@{‘whatif’=$NULL}

Or

$Safety=@{}

To work with those cmdlets, I can pass two splats to the Active Directory cmdlets that need a safety switch, and change the script globally from the top level as a parameter:

REMOVE-ADuser JohnSmith @AdParams @Safety

The double splat! An available, built-in feature in Windows PowerShell. It’s been sitting there and ready to use the whole darn time!

We now return you to your regularly scheduled scripting!

I invite you to follow the Scripting Guys on Twitter and Facebook. If you have any questions, send email to scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

Sean Kearney, Honorary Scripting Guy and Windows PowerShell MVP 

Your comment has been posted.   Close
Thank you, your comment requires moderation so it may take a while to appear.   Close
Leave a Comment
  • Degrees below Celsius? That put a smile on my face in addition to the double splatting feature. Thank you Cheers Tore

  • A "hash" is not really an array. Double splatting is very useful.

  • $Debug=@{ whatif=$true; Verbose=$true; Warning=$true }

  • Another option (if you're running PowerShell 3.0 or later) is to use the $PSDefaultParameterValues table. For example: $PSDefaultParameterValues['*-AD*:WhatIf'] = $true

  • Is it safe to have the user credentials in the script like this?

  • If you have to ask...

  • Nope, you'd never put credentials in a script. You'd put them in something only the script could reach running under a given context. My preference is using certs against a SQL server to pull creds - this has the added bonus of centralizing passwords for scripts. The credentials that scheduled task runs under can't do ANYTHING other than run the job. The script instantiates a connection against the SQL box via certificate. The script then hits a SPROC which gives back the password. Then that's used to create a secure string. Convoluted? Absolutely. Unbreakable? Hardly. Raising the bar for getting credentials from a script? Certainly.

  • Interesting feature to save the huge command lines inside scripts, and for one point for change (cleaner code). Greetings from +30ºC Brazil!

  • A comment I will leave regarding the "open credentials". You are correct, this is not normally a good way of doing this. In the case of this particular script in Production it actually prompts for the password and userid, it was all dependant on which domain I would have to authenticate to. The setting of the Preferences variable is a good option, but unfortunately what it does it confirm on EVERY change (including writing to log files) The other reason for setting the WHATIF as a Null variable was I encountered an issue with some Cmdlets (cough cough A/D) where it would not accept -whatif:$TRUE. So the other way is to get sneaky :) Cheers all and thanks for the great comments! Sean

  • Just to notice that you can combine hash tables like:
    param
    (
    [string]$ComputerName,
    [pscredential]$Credentials,
    [string]$SessionName,
    [string]$Authentication
    )

    begin
    {

    $InfunParam_ComputerName = @{ComputerName=$ComputerName}
    $InfunParam_Credentials = @{Credential=$Credentials}
    $InfunParam_SessionName = @{Name=$SessionName}
    $InfunParam_Authentication = @{Authentication=$Authentication}

    $InfunParams = $InfunParam_ComputerName + $InfunParam_SessionName + $InfunParam_Authentication

    if ($PSBoundParameters.ContainsKey("Credentials"))
    {

    $InfunParams = $InfunParams + $InfunParam_Credentials

    }

    }

    process
    {

    New-PSSession @InfunParams

    }