Author Archives: tommymaynard

An Advanced Function Template (2.0 Version)

Welcome. If you’re here for the download, it’s toward the bottom of this post.

Today’s post goes hand in hand with a session I gave at the Arizona PowerShell Saturday event on Saturday, October 14, 2017. I didn’t do it previously, but this year especially, it made sense to have a post at tommymaynard.com, as a part of my session at the event. I wanted a place to offer my advanced function template for download, and so this, is it. If you couldn’t attend the event and be a part of the session yourself, then this may be the next best alternative. Well, for my session anyway. This event included sessions from Jason Yoder, Will Anderson, and Jason Helmick. While we’re at it — naming names — many thanks to Thom Schumacher for his role in organizing this event.

Toward the end of 2016, I spent some nights and weekends, and moments in the office too, writing a PowerShell advanced function template. Its main purpose was to include built-in function logging. You see, I wanted logging, but I didn’t want an external logging function to do it, and so I decided that every one of my functions would use the same template, and therefore, I could offer consistent logging capabilities across all my functions. These include my own functions, and even those written and put in place for my coworkers. At last check, I’ve snuck 40 plus tools into production. These include PowerShell functions for Active Directory, Group Policy, Exchange, SharePoint, Office 365, Amazon Web Services, VMware, and general operating system and management needs. There’s always something to automate, and now, when they do get automated, each includes the same base functionality.

I liked it, I use it, and I even made my advanced function template available for download on its original post. After some use, I came to realize that it could’ve been better. If you’ve been at this scripting and automation game for awhile, then you understand that automation, even when it’s done, is never really done. There’s always room for improvement, even if there isn’t always time to execute that mental list of changes, fixes, and increased functionality you want to add to already written automation.

The first thing I needed, which I didn’t even know I needed at first, was a function, and I’m not talking about the template code. I’m talking about a way to demonstrate both advanced function template versions (1.0 and 2.0), using the same non-template code. At first, I thought I’d just walk though the code in my 2.0 version of my advanced function template, but really, it made sense to use an easy to understand previously written function as an example, running in both the 1.0 and 2.0 versions of my advanced function template. At nearly the same time I was prepping for PowerShell Saturday, I had written a function that created random passwords — I know, I know… there’s a bunch of these already. I took that function’s code and wrapped it in my 2.0 version, as it had already been written with my 1.0 version, for use in my session at the PowerShell Saturday event.

All the files I used, are included in the below, free from viruses, zip file. This includes the ArizonaPowerShellSaturday.ps1 file that I used to run all the various commands, the New-RandomPassword1.0.ps1 file (uses the 1.0 version of advanced function template), and New-RandomPassword2.0.ps1 file (uses the 2.0 version of advanced function template), and the blank AdvancedFunctionTemplate2.0.ps1 — this is the one you’re likely after. If you attempt to use the first file mentioned, ArizonaPowerShellSaturday.ps1, then you’ll need to modify the first region, where the variables are assigned, so that they point to the other three files, wherever you decided to save them. Also, there’s a couple references to an alias I use, called code. I don’t believe this is a built-in alias, so the line won’t work as expected on other people’s systems. Know that the idea behind those lines is to open the referenced file inside of Visual Studio Code.

ArizonaPowerShellSaturday2017AllFiles (139 downloads)

Update: I was asked at the PowerShell Saturday event, what kind of license I had. Ugh, none. But, for the sake of those that need it, let’s distribute this under the MIT License.

Copyright 2017

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Add the ISE’s Ctrl + M to Visual Studio Code

As suspected, by me at least, the more I use Microsoft Visual Studio Code, the more I’m going to want to modify it. Remember, I just came into the light with the recent addition of Region support in version 1.17. This desire to modify is no more true than seen in an edit I made today to the keybindings.json file. This file allows one to override the default keyboard shortcuts in order to implement “advanced customizations.” It’s pretty awesome, and well appreciated.

Today I added the section for the Ctrl + M keyboard combination, such as can be seen in the last, or third, section of the below JSON. What’s this do, right? If we think back to the ISE (Microsoft’s Integrated Scripting Environment) you may remember that Ctrl + M collapsed all collapsible sections in the current script, or function. With this change in Visual Studio Code, I can now continue to use Ctrl + M to quickly collapse all the sections in my active function, or script. This is until I realize what the default action of Ctrl + M — Toggle Tab Key Moves Focus — actually does, and I find it necessary. I do want to mention, that I could’ve just went with the new keyboard combination of Ctrl + K Ctrl + 0. Ugh, no thanks for now.

[
    { "key": "ctrl+`",      "command": "workbench.action.terminal.focus",
                               "when": "!terminalFocus"},
    { "key": "ctrl+`",      "command": "workbench.action.focusActiveEditorGroup",
                               "when": "terminalFocus"},
    { "key": "ctrl+m",      "command": "editor.foldAll",
                               "when": "editorTextFocus"} 
]

While we’re here, I might as well mention (a.k.a. help myself remember), what the first two sections in my keybindings.json file do, as well. These allow me to use Ctrl + ` to switch focus between the editor on top, where I write my code, and the terminal below, where I can run PowerShell commands interactively. While that’s all for this post, I won’t be surprised if I’m back here updating it with new additions I make to my keybindings.json file.

As a newbie to Visual Studio Code for PowerShell development, I already don’t like even seeing the ISE. It’s pretty amazing what Region support in Visual Studio Code did to me.

Visual Studio Code Regions

It’s happened.

Microsoft has figured out how to get regions to work in Visual Studio Code. I thought it, and I may have even said it too, but it’s been my holdout for not using Microsoft’s code editor for PowerShell. While I’ve been using Visual Studio Code for my AWS YAML creation without regions, I hadn’t been ready to give up the ISE (Integrated Scripting Environment). Well, as of today, those days are over.

So what’s a region, right? It’s an easy way to collapse a section of code that isn’t collapsible by default. I greatly suspect I first learned about them from Ed Wilson — the original scripting guy. Here’s a couple examples from my old friend, the ISE. In the first example you can see the region sections aren’t collapsed and therefore display the commands. In the second image, you can’t see the commands at all. This becomes quite helpful when the region is loaded full with code and commands that simply don’t always need to be seen.

Here’s the same examples in Visual Studio Code.

What a glorious day! After being all set to use the ISE for the upcoming Arizona PowerShell Saturday event (2017), due to a lack of region support in VS Code, I’m glad to report that I’m going to go ahead and use it for my session. I didn’t see that one coming!

Finally, here’s an example of nested regions. I often do this as well, and these seem to work as expected.

Stickers, What!?

When I started tommymaynard.com, I never considered that I would ever brand anything with my URL, and yet, as of this weekend I have. There’s a couple reasons as to why.

One, I’ll be in Phoenix in a couple weekends to give a session about my newest advanced function template (the 2.0 version). As a part of the session, I’m also producing a related blog post, so people at the session can refer to it in case they miss something during my session, or in case I run short on time. I couldn’t believe how fast an hour went when I spoke last year. Additionally, I’ll use my blog to make the advanced function template available — think, downloadable.

Two, it’s the PowerShell symbol and who, that’s willing to lose some time to PowerShell on a Saturday, wouldn’t want a sticker with that logo? So, for those that attend, I’ll see you there, and for those that don’t, let me see you here after 11 a.m. on Saturday, October 14, 2017 to check out the post.

Read-Host with a Previous Value Part II

It’s simply not everyday that Rob Sewell (@sqldbawithbeard) places a link on Twitter to your blog. Yours, as in mine, but here we are. That was yesterday.

Today, knowing this is out there, I decided to add a bit more to this short post with a second installment. In this post, I’ve written a few changes to the single If statement from Part I. While it was a simple statement to see if I could reuse a previously stored value in the first installment, it’s a bit more full featured now.

We’re getting away from statically setting the default user. We’ll actually let the user assign that value. This determination — whether there’s a default user or not — defines our prompt message, which is most of what’s been added.

If ($User) {
    $Prompt = "Press Enter for the last user [$User], or enter a new user"
} Else {
    $Prompt = "Please enter a new user"
}

If (($Result = Read-Host -Prompt $Prompt) -eq '' -or $Result -eq $User) {
    "You're using the previously used ""$User"" user."
} Else {
    "You're using the previously unused ""$Result"" user."
    $User = $Result
}

I won’t bother including any examples of the running code, but do run the example yourself if you’re interested. Remember to clear or remove the $User, $Prompt, and $Result variables if you want to start fresh. The following command will remove those variables, if you find you need it before a new run.

Remove-Variable -Name User,Prompt,Result -ErrorAction SilentlyContinue

And that’s it. Thanks for the link to the blog, Rob!

Three Ways to Set $PSDefaultParameterValues Part II

In part one of this post, I indicated three different ways to populate the $PSDefaultParameterValues preference variable. The $PSDefaultParameterValues variable allow one to assign default values to the parameters of cmdlets and functions without ever needing to type them when they invoke the cmdlet or function. Here’s the two sets of examples from that post.

$PSDefaultParameterValues.Add('Get-Help:ShowWindow',$true)

$PSDefaultParameterValues = @{'Get-Help:ShowWindow' = $true}

$PSDefaultParameterValues['Get-Help:ShowWindow'] = $true


$PSDefaultParameterValues.Add('Out-Default:OutVariable','__')

$PSDefaultParameterValues = @{'Out-Default:OutVariable' = '__'}

$PSDefaultParameterValues['Out-Default:OutVariable'] = '__'

In the first section of the above example, we set the ShowWindow parameter of Get-Help to $true. Now, whenever I use Get-Help, it will always include the ShowWindow parameter, and I don’t have to type a thing. Keep in mind, if it’s not clear, that the first three commands all do the same thing. You’d only need to run one of them. The same goes for the second section of the above example.

So, I recently wrote a function at work. It’s another PowerShell random password generator, as if the world needed one more. In my version, there’s a CharacterType parameter that can accept one or more of four predefined values: Lowercase, Number, Symbol, and Uppercase. These values can be used alone, or in combination with each other. As I have a coworker that didn’t want symbols, I obliged by adding this character type feature. I should mention that if the CharacterType parameter isn’t included at all, that it will default to use all four character types. As it should.

Now, my coworker doesn’t have to type the below command over and over in order to include three of the four possible values, every time.

PS > New-RandomPassword -CharacterType Lowercase,Number,Uppercase

That’s when it dawned on me. I’m not sure I’ve ever used the $PSDefaultParameterValues preference variable with multiple parameter values. It’s up there, but to review, the function is New-RandomPassword, and the parameter where we want default values is called CharacterType. The default values we always want included are Lowercase, Number, and Uppercase. As you’d expect, we’re avoiding symbols in our random passwords for this example.

$PSDefaultParameterValues = @{'New-RandomPassword:CharacterType'='Number','Uppercase','Lowercase'}

$PSDefaultParameterValues['New-RandomPassword:CharacterType'] = 'Number','Uppercase','Lowercase'

$PSDefaultParameterValues.Add('New-RandomPassword:CharacterType',@('Number','Uppercase','Lowercase'))

Although you’d only need to run one of them, each of the above examples will modify $PSDefaultParameterValues the identical way.

PS > $PSDefaultParameterValues | Format-Table -AutoSize

Name                             Value
----                             -----
New-RandomPassword:CharacterType {Number, Uppercase, Lowercase}

Now, whenever New-RandomPassword is invoked (on a system where this entry is a part of $PSDefaultParameterValues, obviously), it’ll include those values, for that parameter, without the need to actually type the parameter name and the values — neat. As of now, I can say I’ve include multiple values for a single parameter using the $PSDefaultParameterValues variable. You too, or at least you can say you know it’s an option.

Read-Host with a Previous Value

Update: There’s a Part II, read it next.

Here’s a quick one. For an upcoming project, I decided I may need a way to use Read-Host in combination with a previously set, default value. Here’s the working code I threw together in case it’s needed.

$User = 'tommymaynard'

If (($Result = Read-Host -Prompt "Press Enter for the last user [$User], or enter a new user") -eq '' -or $Result -eq $User) {
    "You're using the previously used ""$User"" user."
} Else {
    "You're using the previously unused ""$Result"" user."
}

I set my user to “tommymaynard,” and then the If statement executes. The execution pauses while the Read-Host cmdlet waits for my input. If I press Enter, the If portion runs indicating that I’m using the previously set value of “tommymaynard.” It’ll do that if I enter tommymaynard again, too. If I enter a new value, it indicates I’m using the new value.

While we’re here, let’s run the code with three possible values: nothing entered, tommymaynard entered, and a different user entered.

Press Enter for the last user [tommymaynard], or enter a new user:
You’re using the previously used “tommymaynard” user.

Press Enter for the last user [tommymaynard], or enter a new user: tommymaynard
You’re using the previously used “tommymaynard” user.

Press Enter for the last user [tommymaynard], or enter a new user: lsmith
You’re using the previously unused “lsmith” user.

And there it is, a super short post about using a previously set value for Read-Host.

AWS UserData Multiple Run Framework Part III

At the bottom of the below post (Part II), was a function that created code for what I called the AWS UserData Multiple Run Framework. The code produced by this function can be added to the AWS CloudFormation UserData section allowing for Windows EC2 instance configuration between a controlled number of restarts. You know… configure, restart, configure, restart, configure, restart, etc. Without this in place, or some other configuration tool, you only get one time to utilize the UserData section, and that’s when an EC2 instance launches for the first time.

That version, however, required the use of text files at the root of the C:\ drive. These were used so that the UserData code would know which section of the code to run after each restart. I always worried that someone wouldn’t know the importance of the text files at the root of the C:\ drive and remove them. Therefore, I wrote a newer version. I believe I previously mentioned that as a possibility, in one of the two other related posts.

AWS UserData Multiple Run Framework Part II

This version — the newest version — makes use of the Windows Registry to make the determination of what code to run next. There’s no more scattered files on the root of the C:\, and instead, everything is better protected, and hidden, in the Windows Registry. I’ll put the entire function at the bottom of today’s post, but let’s briefly discuss each section first. Remember, the function at the bottom of this post is the function that creates the code I’ll discuss. It creates the code that you would add to the AWS CloudFormation UserData section. It isn’t the code you’d add to the UserData section. It’s still PowerShell creating PowerShell. For reference, think of the Microsoft function New-IseSnippet of old, and the New-ModuleManifest cmdlet. They’re both PowerShell, that create PowerShell.

Function Set-SystemForNextRun {
    Param (
        [string]$Pass,
        [switch]$UserData,
        [switch]$Restart
    )
    If ($Pass) {
        [System.Void](New-ItemProperty -Path 'HKLM:\SOFTWARE\UITS' -Name "Pass$Pass" -Value 'Complete')
    }
    If ($UserData) {
        $Path = "$env:ProgramFiles\Amazon\Ec2ConfigService\Settings\config.xml"
        $ConfigXml = Get-Content -Path $Path
        ($ConfigXml.Ec2ConfigurationSettings.Plugins.Plugin |
            Where-Object -Property Name -eq 'Ec2HandleUserData').State = 'Enabled'
        $ConfigXml.Save($Path)
    }
    If ($Restart) {
        Restart-Computer -Force
    }
}

The Set-SystemForNextRun function — the first part of the code that’s created — has some minor changes. First, the PassFile parameter is now just Pass. This was changed because, well, we’re not using files any more. Second, the code inside the If ($Pass) section no longer creates text files and instead, creates registry values. Remember, as this code runs inside the AWS CloudFormation UserData section, this function is written to memory before it’s ever used. The two upcoming sections are actually run, or rather, do things we can see, first.

# Check if Registry Subkey does not exist.
If (-Not(Get-Item -Path 'HKLM:\SOFTWARE\DEPT' -ErrorAction SilentlyContinue)) {
    # Create Registry Subkey.
    [System.Void](New-Item -Path 'HKLM:\SOFTWARE\' -Name 'DEPT')
}

The above code is actually the first run code in UserData, as again our Set-SystemForNextRun function is written to memory and yet to be invoked. Its purpose is to create a Windows Registry subkey DEPT if it doesn’t already exist. You can change DEPT to whatever makes the most sense for your use. This is the location where we’ll store our values that indicate which section in the upcoming If-ElseIf (ElseIf, ElseIf, etc.) statement we’ll run.

If (-Not((Get-ItemProperty -Path 'HKLM:\SOFTWARE\DEPT').Pass1 -eq 'Complete')) {

    # Place code here (1).

    # Invoke Set-SystemForNextRun function.
    Set-SystemForNextRun -Pass '1' -UserData -Restart

} ElseIf (-Not((Get-ItemProperty -Path 'HKLM:\SOFTWARE\DEPT').Pass2 -eq 'Complete')) {

    # Place code here (2).

    # Invoke Set-SystemForNextRun function.
    Set-SystemForNextRun -Pass '2'

}

This is the meat of what’s produced by the upcoming function. It’s the If-ElseIf statement. On the first run of the UserData, the If statement will fire. This is because we won’t have a value called Pass1 set to the string “Complete” in the HKLM:\SOFTWARE\DEPT subkey. After its code is done, it’ll invoke the Set-SystemForNextRun function which will add the Pass1 value to the Registry, set UserData to enabled, and restart the instance. On the next run, it’ll execute the code in the first (and only) ElseIf section because the value Pass1 will have been created, but the value Pass2 will not have been created. It gets created after the code in this section is complete.

That’s it. Hopefully this can be helpful for more than just me and those around me at work.

Function New-AWSMultiRunTemplate {
    [CmdletBinding()]
    Param (
        [Parameter()]
        [ValidateRange(1,10)]
        [int]$CodeSectionCount = 2,

        [Parameter()]
        [ValidateSet('All','AllButLast')]
        [string]$EnableUserData = 'AllButLast',

        [Parameter()]
        [ValidateSet('All','AllButLast')]
        [string]$EnableRestart = 'AllButLast'
    )

    DynamicParam {
        # Create dynamic, Log parameter.
        If ($PSBoundParameters['Verbose']) {
            $SingleAttribute = New-Object System.Management.Automation.ParameterAttribute
            $SingleAttribute.Position = 1

            $AttributeCollection = New-Object System.Collections.ObjectModel.Collection[System.Attribute]
            $AttributeCollection.Add($SingleAttribute)

            $LogParameter = New-Object System.Management.Automation.RuntimeDefinedParameter('Log',[switch],$AttributeCollection)
            $LogParameter.Value = $true
 
            $ParamDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary
            $ParamDictionary.Add('Log',$LogParameter)
            return $ParamDictionary
        } # End If.
    } # End DynamicParam.

    Begin {
        #region Create logs directory and Write-Verbose function.
        If ($PSBoundParameters['Verbose'] -and $PSBoundParameters['Log']) {
            $LogDirectory = "$($MyInvocation.MyCommand.Name)"
            $LogPath = "$env:SystemDrive\support\Logs\$LogDirectory"
            If (-Not(Test-Path -Path $LogPath)) {
                [System.Void](New-Item -Path $LogPath -ItemType Directory)
            }
            $LogFilePath = "$LogPath\$(Get-Date -Format 'DyyyyMMddThhmmsstt').txt"

            Function Write-Verbose {
                Param ($Message)
                Microsoft.PowerShell.Utility\Write-Verbose -Message $Message
                Microsoft.PowerShell.Utility\Write-Verbose -Message "[$(Get-Date -Format G)]: $Message" 4>> $LogFilePath
            }
        }

        # Set Write-Verbose block location.
        $BlockLocation = '[BEGIN  ]'
        Write-Verbose -Message "$BlockLocation Entering the Begin block [Function: $($MyInvocation.MyCommand.Name)]."
        #endregion

        Write-Verbose -Message "$BlockLocation Storing the template's function to memory."
        $TemplateFunction = @"
Function Set-SystemForNextRun {
    Param (
        [string]`$Pass,
        [switch]`$UserData,
        [switch]`$Restart
    )
    If (`$Pass) {
        [System.Void](New-ItemProperty -Path 'HKLM:\SOFTWARE\DEPT' -Name "Pass`$Pass" -Value 'Complete')
    }
    If (`$UserData) {
        `$Path = "`$env:ProgramFiles\Amazon\Ec2ConfigService\Settings\config.xml"
        `$ConfigXml = Get-Content -Path `$Path
        (`$ConfigXml.Ec2ConfigurationSettings.Plugins.Plugin |
            Where-Object -Property Name -eq 'Ec2HandleUserData').State = 'Enabled'
        `$ConfigXml.Save(`$Path)
    }
    If (`$Restart) {
        Restart-Computer -Force
    }
}

# Check if Registry Subkey does not exist.
If (-Not(Get-Item -Path 'HKLM:\SOFTWARE\DEPT' -ErrorAction SilentlyContinue)) {
    # Create Registry Subkey.
    [System.Void](New-Item -Path 'HKLM:\SOFTWARE\' -Name 'DEPT')
}


"@
    } # End Begin.

    Process {
        #region Set Write-Verbose block location.
        $BlockLocation = '[PROCESS]'
        Write-Verbose -Message "$BlockLocation Entering the Process block [Function: $($MyInvocation.MyCommand.Name)]."
        #endregion

        Write-Verbose -Message "$BlockLocation Beginning to create the If-ElseIf code for the template."
        1..$CodeSectionCount | ForEach-Object {
            If ($_ -eq 1) {
                $Start = 'If'
            } Else {
                $Start = 'ElseIf'
            }

            If ($EnableUserData -eq 'All') {
                $UserData = '-UserData '
            } ElseIf ($_ -eq $CodeSectionCount) {
                $UserData = $null
            } Else {
                $UserData = '-UserData '
            }

            If ($EnableRestart -eq 'All') {
                $Restart = '-Restart'
            } ElseIf ($_ -eq $CodeSectionCount) {
                $Restart = $null
            } Else {
                $Restart = '-Restart'
            }

            $TemplateIfElseIf += @"
$Start (-Not((Get-ItemProperty -Path 'HKLM:\SOFTWARE\DEPT').Pass$_ -eq 'Complete')) {
    
    # Place code here ($_).

    # Invoke Set-SystemForNextRun function.
    Set-SystemForNextRun -Pass '$_' $UserData$Restart

} $End
"@
        } # End ForEach-Object.
    } # End Process.

    End {
        #region Set Write-Verbose block location.
        $BlockLocation = '[END    ]'
        Write-Verbose -Message "$BlockLocation Entering the End block [Function: $($MyInvocation.MyCommand.Name)]."
        #endregion

        Write-Verbose -Message "$BlockLocation Displaying the DEPT AWS mulitple run code with $CodeSectionCount code section(s)."
        "$TemplateFunction$TemplateIfElseIf"
    } # End End.
} # End Function: New-AWSMultiRunTemplate.

On Disk Credentials

This article has been written by plenty of people, plenty of times. But even so, I’m writing it again. This is partly because I want to have a place to find this information written by myself. That said, if it helps anyone else too, then it’s doing the other part, and that’s helping those around me.

What I want to do today, is create an on disk credential file. I’ve done it a few times now, for a few projects, but there’s something about this topic, I can’t forever nail down in my own brain. Well, as best as I see it, those days are numbered.

Once my on disk credential file is created, I want to run an automated task(s) as a user other than that which is running my outlying automation. It’s kind of like this: User1 runs an automated task and it needs to complete some work it can’t do on it’s own. Therefore, it does a part of what it needs as User2. That’s the user that can do what User1 can’t.

The first step is creating the credential file. Now, when you think of credentials, you should think of the combination of a username and a corresponding password, together. That said, this credential file is actually only going to hold the password. While I’m going to continue to call it a credential file, keep in mind that we’re only storing the password inside this file.

In the below example, we’re taking a password, as a standard string, converting it to a secure string, converting that (the secure string) into an encrypted standard string, and writing it out to an on disk file. Before we begin, we need to open our PowerShell host (the ConsoleHost, the ISE, etc.). Based on User1 and User2, we’d open PowerShell as User1. Remember, User1 is going to run our script, function, automation, etc., but it’s going to require creating a credential object — a username and password — for User2.

One other consideration to keep in mind is the computer on which this password file will be used. You have to use the credential file on the computer in which it’s created, and with the user that created it.

$PasswordAsString = '1234)(#DGTh@ppYMedixM.'
$PassOut = ConvertTo-SecureString -String $PasswordAsString -AsPlainText -Force
$PassOut | ConvertFrom-SecureString | Out-File -FilePath 'C:\Users\tommymaynard\Desktop\cred.txt'

Here’s what each line in the above example accomplishes:

Line 1: Creates a variable to hold the password as a standard string. A standard string is like any other string, such as the word dog, cat, or house. Each of those is a standard string, and most often just called a string. We’re including the word “standard,” as a way to differentiate among the other string types we’ll discuss.
Line 2: Creates a variable to hold the password once it’s been converted to a secure string. The AsPlainText and Force parameters are required for ConvertTo-SecureString to accept a standard sting for conversion.
Line 3: Converts the password as stored in the variable created in Line 2 as a secure string, to an encrypted standard string and saves it in an on disk file called cred.txt on my desktop.

Here’s the thing: The above process is a one time thing. Once the on disk credential file is created, then that step will never have to be duplicated (unless you change User2’s password). Remember, you have to open PowerShell as the user that will use the password, not as the account to which the password corresponds.

The next bit of code is how we use the on disk credential file.

$User = 'domain\User2'
$PassIn = Get-Content -Path 'C:\Users\tommymaynard\Desktop\cred.txt' | ConvertTo-SecureString
$Credential = New-Object System.Management.Automation.PSCredential -ArgumentList $User,$PassIn

Here’s what each line in this example accomplishes:

Line 1: Creates a variable to hold the username of the user that corresponds to this password.
Line 2: Creates a variable to hold the password. This command will read in the contents of our cred.txt file from our first example, and convert the encrypted standard string in the file to secure string.
Line 3: Creates a variable to store the credential object. This includes both the username from line 1, and the password we brought in from line 2.

That’s it. Again, this is a two step process. The first example was how to prestage our credential file, and the second example allows us to make use of it. The first example would likely be done once, manually, and as part of a prestaging effort. The second example would be done inside of the script, etc., to make use of running something as a user (User2) other than the one running the script, etc. itself (User1).

Okay Tommy, you now have a place to turn once you’re forgotten how to do this. You too, Internet.

Technical Fact at PowerShell Launch

When I read books, or websites, and find worthy facts, I aim to try and keep them. If I read a book, my bookmark is often a few pieces of paper stapled together with info and page numbers from the book. Well, I’m scrapping that technique, which went hand-in-hand with folding down page corners. I’m also ditching the small pieces of paper that litter my desk with random bits of information: “PowerShell objects come from classes,” and “LastLogonDate is the converted version of LastLogonTimeStamp.” Now, it’s all slated to go in a single file called InfoLines.txt in Dropbox, here: C:\Users\tommymaynard\Dropbox\PowerShell\Profile.

If you’re wondering why I use a Dropbox folder it’s because I want this file and its worthy facts to be available regardless of whether I’m on my work, or home computer. You can read more in a post I wrote that uses Dropbox to sync my profile script between work and home, here: http://tommymaynard.com/sync-profile-script-from-work-to-home-2017. It may help make what I’m doing here make more sense.

For now, because I just started this today, I only have a few lines of worthy information. Here’s the contents of my InfoLines.txt file so far. If you can’t tell, I’m finishing up Amazon Web Services in Action. I only have 70 more pages to go!

The auto-scaling group is responsible for connecting a newly launched EC2 instance with the load balancer (ELB). (AWS in Action, p.315)
DevOps is an approach driven by software development to bring development and operations closer together. (AWS in Action, p.93)
Auto-scaling is a part of the EC2 service and helps you to ensure that a specified number of virtual servers are running. (AWS in Action, p.294)

Each time I open the ConsoleHost, the ISE, or Visual Studio Code, I want a random line from the file to be shared with me. The below code will return a single, random line with asterisks both above and below it. This is in order to help separate it from the (totally unnecessary, unwelcome, and shouldn’t even be there) message that tells me how long my “personal and system profiles” took to load, and my prompt. They need to put that message in a variable and not on my screen without permission. Don’t tell us what you think we need to know, guys.

'**************'
Get-Content -Path "$env:USERPROFILE\Dropbox\PowerShell\Profile\InfoLines.txt" | Get-Random
'**************'

That’s it. Now, whenever I open one of these PowerShell hosts, I’ll get a quick reminder about something I found important, and want to keep fresh in my mind.

Update: I decided I wanted the asterisks above and below my technical fact to go from one end of the PowerShell host to other. Here’s how I did that.

'*' * ($Host.UI.RawUI.BufferSize.Width - 1)
Get-Content -Path "$env:USERPROFILE\Dropbox\PowerShell\Profile\InfoLines.txt" | Get-Random
'*' * ($Host.UI.RawUI.BufferSize.Width - 1)