Creating Multiple Credential Objects

Download the complete function: https://gist.github.com/tommymaynard/98031ccd5de67005bf3063db06a33851

There are times when you made need to use additional credentials, other than those used to begin the Windows PowerShell session. When I need to PSRemote to another domain’s computer, I quickly run a function I have stored in my $PROFILE to create a variable that contains a credential object for the second domain. It’s a bit more specific for my environment, so I won’t bother sharing that exact function. What I will do, however, is share and explain a function I’ve written to create up to 10 credential objects. You’ll soon see where that can be changed (if for some reason someone would want more than that many). Realistically, 10 seems much too high anyway. Moving on.

Now, it might be important to know a bit more about how this started. The unfortunate thing about that function (the one in the link) is that it is maxed out at three credential sets (and it continually used the word ‘domain’). As well, it wasn’t as flexible as it should’ve been and it didn’t have any comment-based help or any verbose statements. So, a couple of days after publishing that post, I copied the function back into the PowerShell ISE and started working on a “1.1” version. That’s what we’ll discuss in this post.

First, we’ll write some basic, structural code for the advanced function.

Function New-TMMultiCred {
    [CmdletBinding()]
    Param ()

    Begin {
    } #End Begin

    Process {
    } #End Begin
} #End Function

Now, let’s add the parameter that will define how many credential objects the function will create. The variable we’ll use is $Set and we’ll cast it as an integer (Set will, therefore, also be the name of the parameter). In addition, we’ll add code to define the -Set parameter as being mandatory (it must be included when the function is run), and make the parameter positional (the value for -Set can be entered without providing the -Set parameter name). In addition, we’ll add the ValidateRange validation attribute that will require that the integer entered, as the value for the -Set parameter, must be 1 through 10. This can be changed if necessary.

Function New-TMMultiCred {
    [CmdletBinding()]
    Param (
        [Parameter(Mandatory=$True,Position=0)]
        [ValidateRange(1,10)]
        [int]$Set
    )

    Begin {
    } #End Begin

    Process {
    } #End Begin
} #End Function

We won’t need to do anything in the Begin block, so we’ll focus on the Process block next. In there, we’ll need to prompt for a user name and password as many times as the value of the parameter -Set indicates. Since we know the number of times we’ll be looping (to prompt for the username and password), I recommend we use a for statement. For statements work this way: set a variable ($i in our case) as a counter variable, add a comparison to determine how many times to loop (while $i is less than or equal to $Set), and finally, include a way to increment the counter variable (that’s what $i++ does), so that we only loop the proper number of times.

Function New-TMMultiCred {
    [CmdletBinding()]
    Param (
        [Parameter(Mandatory=$True,Position=0)]
        [ValidateRange(1,10)]
        [int]$Set
    )

    Begin {
    } #End Begin

    Process {
        For ($i = 1; $i -le $Set; $i++) {

        }
    } #End Begin
} #End Function

The final piece is adding the parts necessary to prompt for usernames and passwords, and creating variables to store each of the credential objects. We’ll do this as part of a try-catch.

Line 15 below, first runs the Get-Credential cmdlet. We know this because it is in parenthesis. These parentheses indicate that this cmdlet needs to run before it’s used as the value of the Set-Variable‘s -Value parameter. If for some reason the user presses Cancel, or presses the X in the top-right corner of the prompt dialog, the try portion of the try-catch will fail, and the catch portion will run. It will indicate that no credential was created for that iteration through the loop.

If the user enters, at minimum a username (because a password can be blank), then it will set a variable called $CredSet# (the hash mark (#) indicates a number). If we indicate we want to create two credential objects when we run the function (New-TMMultiCred 2), then $CredSet1 will be the variable that holds the first credential object, and $CredSet2 will hold the second.

Still on line 15, notice that the -Scope parameter is being used with the Set-Variable cmdlet. If we didn’t include this parameter and its value, Global, then the variables created (or modified, if the variable(s) already existed) by this command would not be available after the function was done executing.

Function New-TMMultiCred {
    [CmdletBinding()]
    Param (
        [Parameter(Mandatory=$True,Position=0)]
        [ValidateRange(1,10)]
        [int]$Set
    )

    Begin {
    } #End Begin

    Process {
        For ($i = 1; $i -le $Set; $i++) {
            try {
                Set-Variable -Name CredSet$($i) -Value (Get-Credential -Credential $null) -Scope Global
                Write-Output -Verbose "SUCCESS: Credential Set $i stored in `$CredSet$($i)"
            } catch {
                Write-Warning -Verbose "No credential object was created for set $($i)."
            }
        }
    } #End Begin
} #End Function

Here’s a look at the function in progress. The first image shows that three credential objects were requested. The first credential object has already been created and is stored in $CredSet1, the second wasn’t created, since the user pressed Cancel on the second prompt, and the third credential object will be created when the user presses OK. The second image shows the end result.

Function for Creating Multiple Credential Objects-01

Function for Creating Multiple Credential Objects-02

Once this is complete, the user can run cmdlets that have an optional -Credential parameter and supply the fitting credential object, as seen in the example below. You can return all the cmdlets and functions that have the -Credential parameter using Get-Command: Get-Command -ParameterName Credential.

PS C:\> Invoke-Command -ComputerName dc01 -ScriptBlock {Get-Date} -Credential $CredSet3

Download the complete function: https://gist.github.com/tommymaynard/98031ccd5de67005bf3063db06a33851

Three Sets of Credentials for Three (or the Same) Domains

I briefly scanned this topic on Reddit’s PowerShell subreddit. I’m not really sure if this will help anyone, but the first thing I thought of was to create an advanced function that could prompt for up to three different sets of credentials, each for a different domain. It’s loosely based on a function I have in my $PROFILE that I use for a second domain. It should be noted that you don’t have to use the function for different domains. It can be used for different user accounts in the same domain—that part is up to you.

Function New-TMMultiDomainCred {
    [CmdletBinding()]
    Param (
        [switch]$Domain1,
        [switch]$Domain2,
        [switch]$Domain3
    )

    Begin {
    } #End Begin

    Process {
        Switch ($PSBoundParameters) {
            {$PSBoundParameters.Keys -contains 'Domain1'} {
                $Global:Domain1Cred = Get-Credential -Credential $null
                Write-Output -Verbose 'Domain Creds for Domain1 stored in $Domain1Cred'
            }
            {$PSBoundParameters.Keys -contains 'Domain2'} {
                $Global:Domain2Cred = Get-Credential -Credential $null
                Write-Output -Verbose 'Domain Creds for Domain2 stored in $Domain2Cred'
            }
            {$PSBoundParameters.Keys -contains 'Domain3'} {
                $Global:Domain3Cred = Get-Credential -Credential $null
                Write-Output -Verbose 'Domain Creds for Domain3 stored in $Domain3Cred'
            }
            default {
                Write-Warning -Verbose 'Parameters must be provided to use this advanced function.'
            }
        }
    } #End Process
} #End Function

Checking a Variable for a Path

I helped someone on a forum recently that needed to determine if a path existed or not. Providing the path did exist, they would need to perform an action. The problem wasn’t what to do (in the end), it was determining if the path existed (to begin with). Here’s a rough draft of their code:

$Path = 'C:\Scripts\TestFolder'

If ($Path) {
    Remove-Item -Path $Path
}

If you’ve been doing this a little while, then it’s quite easy to spot the problem with the “logic” used in the example above. The If statement’s condition is only evaluating if $Path has a value (if it contains something). The fact that the value may, or may not, be a path, and whether or not it exists (if it is a path), is not being evaluated. I recommended they use the Test-Path cmdlet, to ensure the value stored in $Path, was a path, and that it existed.

$Path = 'C:\Scripts\TestFolder'

If (Test-Path -Path $Path) {
    Remove-Item -Path $Path
}

There are times when all you will want to know is if a variable contains a value. As an example, we can use a variable to record if a specific process is running (at the time the variable is created), and take action depending on if it is, or isn’t.

The example below checks for a process called pn (Programmer’s Notepad) and stores the results in the $PNProcess variable. We can then check and determine if $PNProcess has been set, or assigned a value, and make a determination of what to do based on the results. Since we’re only looking to see if our variable contains a value, then this is a perfectly suitable solution.

$PNProcess = Get-Process -ProcessName pn -ErrorAction SilentlyContinue

If ($PNProcess) {
    Write-Output -Verbose "Programmer's Notepad is running."
} Else {
    Write-Output -Verbose "Programmer's Notepad is not running."
}

Note: While I would normally wrap a string in single quotes, double quotes are being used because single quotes will not work when there’s a single quote in the string.

Intermediate-Level, PowerShell Content Needed

There was a ‘What’s New in PowerShell v5?’ webinar recently. I found one of the questions, that was asked of the participants, quite interesting. The question was, “What skill level of PowerShell content would you like to see on Petri.com?” At 49%, intermediate skill level came in first. It was followed by beginner at 34%, and expert at 18%.

I get that we don’t know the skill level of those that responded, or even the number of people that answered the question. Regardless, I was excited, and I’ll tell you why. I mentioned it here first, but a large part of why I write about Windows PowerShell, is because I want to provide original and current, intermediate-level, PowerShell content.

There was a day, about a year (and, maybe a half ago) that I decided I needed to learn at least one thing about PowerShell every day – to include Saturday and Sunday. It didn’t matter how simple, or complex, it was. You see, I understood that the best way to learn Windows PowerShell was to immerse myself, as much as my regular life – the one with the kids, and my wife – allowed. I wanted to ensure I was reading and learning about PowerShell, whenever possible.

My favorite place to find content was Twitter – a bunch of people, from all over, sharing articles and blogs, and asking questions. It was, and still is, a central place to go and find some random article to encourage my learning. I still search there regularly, and now, since maybe June 2014, I even post my own articles. I don’t know who reads what around here, but I often consider that someone like me is looking for new PowerShell content on Twitter, and I want them to feel like there’s plenty, by doing my part to contribute.

I want to be an expert at PowerShell – there’s no question. But until then, I’ll keep writing for us intermediates – not too basic, or too advanced.

PowerShell Resolutions 2015

I read a recent Boe Prox tweet in regards to PowerShell resolutions for the coming year. I carry around with me a mental list of things I want to do, learn, and accomplish, in PowerShell. Although they are often thought about, I had simply never considered making them resolutions, but I suppose that during this time of the year, I should.

While 2014 was a great year for me and PowerShell, there’s just no way it could be better than 2015. I began to write down my list after reading Boe’s, and I’ve decided that I should post and share mine, as well. Perhaps this will be motivational; perhaps I’ll use this as my official checklist. If PowerShell is important to you and your career, then you should start thinking about, and maybe writing down, your PowerShell resolutions, too.

I can honestly say that I became successful with PowerShell the moment I required myself to learn at least one thing about PowerShell — no matter how small — per day. That, in addition to not allowing myself to get discouraged when I didn’t understand something at my first exposure, has been how I crammed so much knowledge into my brain in the last year. I remember searching Twitter for things to read and learn from, and that folks, is why there’s a tommymaynard.com. Anyway, on to my resolutions:

– Continue to help (and learn) on various PowerShell forums including: Reddit, Microsoft Technet, and PowerShell.org.
Update 1: As of May 2015, and I’m still reading and assisting on the various PowerShell forums.
Update 2: It’s December 2015 and I didn’t slow down on helping (and indirectly learning), by assisting on PowerShell forums which included all of those listed above.

– Continue to blog at tommymaynard.com and share my posts on Twitter, and possibly other outlets.
Update 1: As of May 2015, and I’m still producing a fair amount of original content and sharing it on Twitter: https://twitter.com/hashtag/powershell?f=realtime
Update 2: As of August 2015, I linked my first post on the Facebook PowerShell Page.
Update 3: I wrote 61 posts in 2015 — wow!

– Come up with a second submission for the PowerShell.org TechLetter Newsletter.
Note: First submission has been “lined up for publishing,” but has yet to be included—come on January 2015!
Update 1: 1st submission included in January 2015 TechLetter.
Update 2: While this wasn’t a submission to the TechLetter, my series of posts about the PowerShell Summit North America 2015 were linked in the May 2015 TechLetter and on PowerShell.org itself (before just anyone was able to blog on PowerShell.org).
Update 3: I submitted an article for the PowerShell.org TechLetter in hopes to win a 4-day pass to the PowerShell & DevOps Summit 2016. I indicated to run it whether I win, or not. So, perhaps that will show up.

– Express my interest in being a PowerShell.org, TechLetter Editor.
Note: May be mentioned on PowerShell.org in January 2015.
Update: This was never mentioned again by PowerShell.org (in which I’m aware).

– Learn more about, and begin working with, DSC.
Note: First two DSC servers built on December 12, 2014.
Update 1: I’m still working on DSC, but not as much as I would have wanted to by now. There’s still time in the year, and so I plan to return to it soon (May 2015).
Update 2: I took a chance on DSC and good thing I did. My employer heard about Just Enough Administration and wants a Proof of Concept. I’m still working on that…

– Attend, and learn as much as possible from the North America PowerShell Summit 2015.
-and-
– Make PowerShell community contacts (network).
Update: Done and done, and it was awesome.

– Take and Pass the Verified Effective PowerShell exam.
Note: After North America PowerShell Summit 2015.
Update 1: The Verified Effective PowerShell exam is gone.
Update 2: It’s back on, but the test is only offered at the PowerShell Summit 2015: I’m registered!
Update 3: I didn’t pass, that I know of, but I haven’t actually checked. I should probably track down that link. Anyway, I could have completed the test, but there just wasn’t enough time.

– Submit PowerShell presentation abstract for IT conference at (name redacted).
-and, possibly-
– Lead a PowerShell, conference presentation.
Update: My abstract wasn’t ready in time. Even so, I’m not sure this is the right conference for this topic. We’ll see what next year brings.

– Start (name redacted) PowerShell Users Group.
Update: This didn’t happen, but I like the chances I can get something going in the coming year.

– Continue to work toward PowerShell.org Hero, and Microsoft PowerShell MVP.*
Update: I think I’ve done a good deal to help the community, but only time will tell. I’m definitely better off than I was a year ago. I know so much more now, and have no desire to stop. I still love to write about, and help people learn, Windows PowerShell.

* I feel like I should say something about these goals. I’ve read it a time, or two now: “I didn’t set out to be an MVP…” For me, I’m not afraid to openly admit that I want recognition within the PowerShell community. The thing is, if I can line myself up with these recognition programs, then I have proof of what I set out to do from the beginning — help teach PowerShell to those seeking to learn. So sure, I want to be successful, in helping others succeed.

Determine if the Alias, or Function Name was Used

As a Windows PowerShell enthusiast, I am often thinking about PowerShell. Seems logical. Can I do this? What will happen if I do that? Why doesn’t this work!? Well, I had one of those thoughts recently. Can I determine if a function was called using an alias, or if it was called using the function name?

This thought occurred to me because of a change I considered adding to one of my functions. The idea was, that I wanted to prompt the user for confirmation to run a function (within the function), if an alias was used, and not prompt the user when the full function name was used. This lined up with the assumption that an alias could be accidentally entered more easily than the function name, and that we may want to take precautions when the alias was used.

My first consideration was to get the last command entered by the user, by using: (Get-History -Count 1).CommandLine. The problem with that “option,” is that the history isn’t updated until after the function has ended. This means that I wouldn’t know what was entered by the user while the function was running. Take a look at the example below. When I run the function Get-TMHistory the first time, nothing is displayed (in a fresh console), but something is displayed after the second run.

Function Get-TMHistory {
	Get-History -Count 1
}

PS C:\> Get-TMHistory
PS C:\> Get-TMHistory

  Id CommandLine
  -- -----------
   1 Get-TMHistory

I came up with a another idea. Let’s start by creating a new function called Get-TMHowCalled. In addition, we’ll need to create an alias for the function – let’s use ghc. I’ve included the New-Alias cmdlet’s -Force parameter in case there’s already an alias with that name. If there is, then New-Alias acts like Set-Alias, and updates the alias (instead of throwing an error). Note: This option could be problematic if it replaced ghc, and ghc was used for a different purpose.

New-Alias -Name ghc -Value Get-TMHowCalled -Force

Function Get-TMHowCalled {

}

The next thing to do is to construct the logic that will check and determine if the alias was used, or if the full function name was used. This is simple function: check, and echo what was entered by the user.

Function Get-TMHowCalled {
    If () {
        Write-Output -Verbose 'Alias was used.'
    } ElseIf () {
        Write-Output -Verbose 'Function name was used.'
    }
}

The secret here is the $MyInvocation automatic variable and its Line property. This stores what was entered, and so it can be used to determine how the function was called – by alias or by name. The best part, is that the variable is populated when the function is called and can, therefore, be queried inside the function. This variable can only be used in “scripts, functions, and script blocks.” You can read more about it using this command: Get-Help about_Automatic_Variables.

Function Get-TMHowCalled {
    If ($MyInvocation.Line -eq 'ghc') {
        Write-Output -Verbose 'Alias was used.'
    } ElseIf ($MyInvocation.Line -eq 'Get-TMHowCalled') {
        Write-Output -Verbose 'Function name was used.'
    }
}

Hope this proves to be helpful for someone. It might be for me.

Get the Version of PowerShell (Not its Host)

I’ve seen it over and over again: people telling people to use $Host, or the Get-Host cmdlet, to determine the version of Windows PowerShell. Please stop. Get-Host and $Host return a version, sure, but that’s the version of the host – the application that is hosting PowerShell. It’s not the version of PowerShell.

For many of us, the host is the PowerShell console, or the ISE. The name of the console host is ConsoleHost, and the name of the ISE host is ‘Windows PowerShell ISE Host.’ You can return these names by entering $Host or Get-Host, in the respective host, and looking at the Name property. These hosting applications are made by our friends at Microsoft, and so often, if not always, the version of the host will match the version of PowerShell. That said, you’d still be better served to get your results from the proper variable, especially in certain situations.

In order to ensure you are returning the version of PowerShell, use the automatic variable $PSVersionTable. The examples below show how to return information using this variable. Notice how we further isolate the version property – a suitable thing to do when you’re using the correct variable.

PS C:\> $PSVersionTable

Name                           Value
----                           -----
PSVersion                      4.0
WSManStackVersion              3.0
SerializationVersion           1.1.0.1
CLRVersion                     4.0.30319.34014
BuildVersion                   6.3.9600.17090
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0}
PSRemotingProtocolVersion      2.2

PS C:\> $PSVersionTable.PSVersion

Major  Minor  Build  Revision
-----  -----  -----  --------
4      0      -1     -1

PS C:\> $PSVersionTable.PSVersion.Major
4
PS C:\>

We can do the same type of property isolation with $Host and Get-Host. However, to get in this habit while using $Host, or Get-Host, is a bad idea, and may lead to wasted time and confusion if you’re attempting to determine the version of PowerShell.

PS C:\> $Host

Name             : ConsoleHost
Version          : 4.0
InstanceId       : 48ae3046-45ed-46d4-bf6a-0b7e289856db
UI               : System.Management.Automation.Internal.Host.InternalHostUserInterface
CurrentCulture   : en-US
CurrentUICulture : en-US
PrivateData      : Microsoft.PowerShell.ConsoleHost+ConsoleColorProxy
IsRunspacePushed : False
Runspace         : System.Management.Automation.Runspaces.LocalRunspace

PS C:\> $Host.Version

Major  Minor  Build  Revision
-----  -----  -----  --------
4      0      -1     -1

PS C:\> $Host.Version.Major
4
PS C:\>

The problem occurs when we’re using other hosting applications and under the belief $Host, or Get-Host, returns the version of PowerShell. The first instance that comes to mind, where we can see this problem, is when you’re using PSRemoting. If you’re not using the $PSVersionTable variable in a remote session, then you’re likely going to end up with incorrect results, when you try and get the version of PowerShell.

The example below uses an interactive session on a remote computer. $Host says we’re using version 1, but again, it’s reporting the version of the host (this time it’s named ServerRemoteHost). $PSVersionTable correctly indicates we’re using PowerShell 4.0. I’ve also returned the version of the operating system. Microsoft Server 2012 R2, by default, comes with PowerShell 4.0.

PS C:\> Enter-PSSession -ComputerName dc01
[dc01]: PS C:\Users\tommymaynard\Documents> Set-Location \
[dc01]: PS C:\> $Host.Version

Major  Minor  Build  Revision
-----  -----  -----  --------
1      0      0      0

[dc01]: PS C:\> $PSVersionTable.PSVersion

Major  Minor  Build  Revision
-----  -----  -----  --------
4      0      -1     -1

[dc01]: PS C:\> $Host

Name             : ServerRemoteHost
Version          : 1.0.0.0
InstanceId       : aed243b7-73ee-45c8-9032-2f2a5679f6ed
UI               : System.Management.Automation.Internal.Host.InternalHostUserInterface
CurrentCulture   : en-US
CurrentUICulture : en-US
PrivateData      :
IsRunspacePushed :
Runspace         : System.Management.Automation.Runspaces.LocalRunspace

[dc01]: PS C:\> (Get-CimInstance -ClassName Win32_OperatingSystem).Caption
Microsoft Windows Server 2012 R2 Standard
[dc01]: PS C:\> Exit-PSSession
PS C:\>

Use $PSVersionTable to make sure you capture the proper version of PowerShell – not the version of the host, that’s hosting PowerShell. If this is one of your bad habits, then change it now, before you wish you had.

Add CMD’s ver to PowerShell

One of the great things about Windows PowerShell is that it can run Windows native command line tools, such as ping, ipconfig, and others. There are times, however, when there are exceptions. While recently working in the console, I brainlessly entered ‘ver’ (without the quotes), and it didn’t return what I expected. Instead of printing ‘Microsoft Windows [Version 6.3.9600]’ to the console, it reported that ver wasn’t recognized.

PS C:\> ver
ver : The term 'ver' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the
spelling of the name, or if a path was included, verify that the path is correct and try again.
At line:1 char:1
+ ver
+ ~~~
    + CategoryInfo          : ObjectNotFound: (ver:String) [], CommandNotFoundException
    + FullyQualifiedErrorId : CommandNotFoundException

While I wouldn’t recommend opening CMD to run the command, because seriously get out of that habit already, you can switch to CMD from inside the PowerShell console, as demonstrated below.

PS C:\> cmd
Microsoft Windows [Version 6.3.9600]
(c) 2013 Microsoft Corporation. All rights reserved.

C:\>ver

Microsoft Windows [Version 6.3.9600]

C:\>exit
PS C:\>

Still, this wasn’t quite as native as I wanted. For whatever reason, I wanted to type ver, and get the identical output I was used to seeing,… regardless of the fact that the ver output doesn’t really tell me much.

The first thing I did was launch the ISE and create an alias for ver. Keep in mind, that you may need to add -ErrorAction SilentlyContinue to the New-Alias cmdlet, if you run the script more than once inside the ISE. The problem here is that you’ll receive an error if you try and create an alias that is already being used. Either that, or you can add the -Force parameter and make New-Alias act like Set-Alias (and overwrite the alias even though it’s not really changing it).

New-Alias -Name ver -Value Get-TMVersion

Next, I constructed a function called Get-TMVersion that the alias ver would use. Inside the function, I created a single variable, $OS, and assigned it the results of a Get-CimInstance command. If for some reason you’re still using PowerShell 2.0, this can be replaced by the Get-WmiObject equivalent: Get-WmiObject -Class Win32_OperatingSystem.

Function Get-TMVersion {
    $OS = Get-CimInstance -ClassName Win32_OperatingSystem
}

Once I have this data stored in a variable, I can begin checking for a value in the variable, and then, building out my results to match the native Windows command. The If statement checks to see if the Name property of $OS begins with the string Microsoft Windows. Providing it does, it sets a second variable, $Version, as seen in the example below. Once that’s complete, it then echos a blank line, the $Version variable, and then echos a second blank line. At this point, $Version should be identical to the ver command’s standard output.

Function Get-TMVersion {
    $OS = Get-CimInstance -ClassName Win32_OperatingSystem
    If ($OS.Name -like 'Microsoft Windows*') {
        $Version = "Microsoft Windows [Version $($OS.Version)]"
    }
    Write-Output -Verbose `r`n$Version`r`n
}

Here’s the function in action.

PS C:\> ver

Microsoft Windows [Version 6.3.9600]

PS C:\>

Boring, but it works. I should note that this function would probably be best served to include some error checking, in case setting the $OS variable errors out. In addition, I suspect there are probably other ways to produce the same resul–… (pause, keyboard keys clicking) …ugh, here’s a couple variations of yet another, simpler way.

PS C:\> cmd /c ver

Microsoft Windows [Version 6.3.9600]
PS C:\> cmd /c ver;echo ''

Microsoft Windows [Version 6.3.9600]

PS C:\>

It seems I could have just dropped a tiny bit of text into my function and called it a day. If you’ve ever read anything else I’ve written and posted, then you may have noticed a pattern. I seem to do things the hard way, long before I figure out a simpler way. I like it that way, though. If anything, it keeps me thinking, and therefore, improving my PowerShell skills overall.

Function Get-TMVersion {
    cmd /c ver
    Write-Output -Verbose ''
}

Get All 13 Months in a Year

I started a little, pet project recently and it’s all based around how I store my pictures and video files. I have a base directory – C:\Users\tommymaynard\Documents\Media – that contains a subdirectory for each year: 2012, 2013, etc. Inside each of those, I have 12 other directories – one for each month. These each use the naming convention 01 – January, 02 – February, etc. Inside each of those directories, I have at least two other directories: pictures and videos. Here’s an image that may help this structure make more sense.

Get All 13 Months in a Year01

I don’t always sort my photos and videos right away. Instead, I’ll often procrastinate and just drop them into a ‘Pics to Sort’ folder on my desktop – that’s literally the directory’s name. I wondered if I would procrastinate any less, if I had Windows PowerShell build out the desired directory structure for me each year. As an aside, if you consider that I always add my pictures and videos to the same I’ll-sort-you-later directory, then I could easily build a solution to do the sorting, based on file extension and creation date, from this directory into the proper subdirectories in the media directory. I’ll save this one for another day, but thanks for thinking of it.

For this Quick Learn, let’s only concern ourselves with capturing the month’s names. While I could have created a hard-coded array of the month names, I decided to see what I could get return using Get-Date – not what I wanted. I could return the current month’s name using Get-Date -Format MMMM, or the current month’s numeric representation using Get-Date | Select-Object Month, but neither of these return all of the months. The system knows the names of the months and so I opted to look for a .NET way to get them.

So, here it is, the System.Globalization.DateTimeFormatInfo .NET class. When you enter this as the value of the New-Object’s -TypeName parameter, it will return a great deal of information, to include a property called MonthNames. In the example below, you can see the code used to store the month names in a variable. You would think I would be done here, but I wasn’t.

PS C:\> $MonthNames = (New-Object System.Globalization.DateTimeFormatInfo).MonthNames
PS C:\> $MonthNames
January
February
March
April
May
June
July
August
September
October
November
December

PS C:\>

Instead of creating directories, like my project does, the example below simply echos the month’s numeric value, a space-dash-space, and then the month’s name. I’ve added a for statement example as well, just in case someone were to wonder why I didn’t include one, since I was incrementing a counter variable and knew how many looping iterations I had to do.

PS C:\> $i = 1
PS C:\> $MonthNames | foreach {Write-Output "$i - $_";$i++}
1 - January
2 - February
3 - March
4 - April
5 - May
6 - June
7 - July
8 - August
9 - September
10 - October
11 - November
12 - December
13 -
PS C:\>
PS C:\> for ($i=1; $i -le $MonthNames.Count; $i++) {echo "$i - $($MonthNames[$i-1])"}
1 - January
2 - February
3 - March
4 - April
5 - May
6 - June
7 - July
8 - August
9 - September
10 - October
11 - November
12 - December
13 -
PS C:\>

Based on these results, the MonthNames property has a thirteenth entry, and that’s a problem, since there’s no thirteenth month. I proved to myself that there was a problem a couple different ways, as seen below. Remember that the first index, or element, of an array is index zero, making January $MonthNames[0] and December $MonthNames[11].

PS C:\> $MonthNames.Count
13
PS C:\> $MonthNames[0]
January
PS C:\> $MonthNames[11]
December
PS C:\> $MonthNames[12]

PS C:\> $MonthNames[13]
PS C:\>

I needed to ensure this extra, blank month wasn’t making its way into my code. While I could have done my filtering when I was actually creating new directories, it is a better practice to only store what I need, and to only store what’s correct. As can be seen below, I only returned twelve months by adding a pipeline and some filtering.

PS C:\> $MonthNames = (New-Object System.Globalization.DateTimeFormatInfo).MonthNames | Where-Object -FilterScript {$_ -notlike ''}
PS C:\> $MonthNames
January
February
March
April
May
June
July
August
September
October
November
December
PS C:\> $MonthNames.Count
12

Since I was already here, I checked out another property. DayNames didn’t have a blank entry, and had I needed to use it, I wouldn’t have had to do any filtering on the returned results – something I incorrectly assumed about MonthNames. Hope this is helpful to someone, and thank you for reading this post.

PS C:\> $DayNames = (New-Object System.Globalization.DateTimeFormatInfo).DayNames
PS C:\> $DayNames
Sunday
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday
PS C:\> $DayNames.Count
7
PS C:\>

Converting to UTC Time (Thanks, SharePoint)

Earlier this year, I created and implemented a constrained, PowerShell endpoint on two SharePoint Central Admin servers. The purpose was to allow non-administrative users the ability to remotely connect via PSRemoting, or PowerShell Web Access, to create new SharePoint Site Collections (SCs). By default, I provided a few Get- SharePoint cmdlets, as well as, the New-SPSite SharePoint cmdlet. A month or so after requesting and then receiving the Set-SPSite cmdlet, the team using the endpoint, requested another cmdlet: Remove-SPSite – a cmdlet that made me a little leery. While I trusted the people who would use the cmdlet, I needed to sleep at night, and so I needed to add some precautions.

So, what to do? I created a “proxy” function in place of the SharePoint Remove-SPSite cmdlet. While the cmdlet wouldn’t be available, the function would be. The best part about writing your own function, for inclusion in a constrained endpoint, has to be the ability to make use of cmdlets in the function, that are not available in the endpoint. The Remove-SPSite cmdlet is used in my function, even when it isn’t available to the people that use the endpoint. Simply, badass.

While I’m not going to share the complete function, I will explain the logic I decided to use. I allowed people using the endpoint, the ability to delete SCs under one condition – that the SC had been created within the last six hours. This helped protect any SCs that had been created prior to six hours ago. To do this, I decided I would simply get the current date and compare it to the created date of the SC – small problem.

First, let’s return the SC that I created.

PS C:\> Get-SPSite -Identity https://teamsite.sp.mydomain.com

Url                                                     CompatibilityLevel
---                                                     ------------------
https:/teamsite.sp.mydomain.com                         15

Now, let’s return the time when the SC was created. The problem, in the example below, is that I didn’t create the site at 2 a.m. From my point in time, that was 7 hours in the future. With the help of a coworker, I realized that the time being used by SharePoint on the back end, was Coordinated Universal Time, or UTC, regardless that all the other SharePoint times were set for my time zone.

PS C:\> (Get-SPSite -Identity https://teamsite.sp.mydomain.com).RootWeb.Created

Monday, November 17, 2014 2:05:10 AM

My first thought was that this wasn’t going to be an effective way to protect the SCs, until I remembered, I can convert the current time. The easiest way to do this was to use the DateTime object’s method, .ToUniversalTime(). That’s right, take the current time, convert it to Universal Time, and then use that for comparison.

In the example below, we do several things. The first three lines set some variables. In line 1, we capture the SC to a variable. In line 2, we create the variable that holds the numeric value of hours we are willing to allow, and in line 3 we create the DateTime object in UTC that we use to compare against the SharePoint SC.

$SPSite = Get-SPSite -Identity https://teamsite.sp.mydomain.com
$Hours = 6
$TimeHoursAgo = ((Get-Date).ToUniversalTime()).AddHours(-$Hours)

If ($SPSite.RootWeb.Created -gt $TimeHoursAgo) {
    Remove-SPSite -Identity $SPSite -Confirm:$True
} Else {
    Write-Warning -Verbose "Unable to delete Site Collection: $($SPSite.Url) (Over $Hours hours old))."
}

The rest of the example does the comparison between the DateTime object we’ve created and the created date of the SC. If the SC was created after our $TimeHoursAgo variable (meaning that it is greater than $TimeHoursAgo), then it can be deleted, and if it wasn’t, then it cannot be deleted.

Notice the .AddHours() method, above, is using a negative sign before the $Hours variable – this is used to move back in time. Also, pay attention to the embedded parenthesis as the value $TimeHoursAgo is being assigned. They indicate the order in which the command is executing, beginning with the most embedded set. You can ignore the parenthesis that are used as a part of the methods, such as .ToUniversalTime(), even if they contain something between the parenthesis. I’ve included an example of what that line would look like had it been written procedurally.

PS C:\> $Hours = 6
PS C:\> $TimeHoursAgo = Get-Date
PS C:\> $TimeHoursAgo = $TimeHoursAgo.ToUniversalTime()
PS C:\> $TimeHoursAgo = $TimeHoursAgo.AddHours(-$Hours)

Keep in mind that various Microsoft technologies may use UTC on the back end, even if the GUI shows your proper time zone.