Category Archives: Quick Learn

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)

Forcing a Switch Parameter to be False

The coworker that sits next to me at the office has upped his PowerShell game tremendously since he’s moved up from Operations to the Windows Team. He often has well-thought-out questions and he’s long shown a great desire to learn about PowerShell. It’s addictive and rewarding, so there’s no question as to why. He may not know it, but I look forward to the questions. The need for PowerShell answers here and there keeps me fresh, and so while it benefits him, it benefits me too.

As he does, he asked a question recently. It was in regard to the Confirm parameter. Why does it need the colon and $false to not be true? Why does it need to be written like this: -Confirm:$false? Why can’t it be written like this: -Confirm $false?

If a switch parameter is included, then its value is $true. If it’s not included, its value is $false. Knowing that alone, makes it clear that we’d have to force something to be false if its default is $true. Okay, knowing that, why can’t we do this then: -Confirm $false?

It’s got to do with the PowerShell parser. It’s how the engine evaluates the statement. Remember, it’s a switch parameter. The parameter is either included or it’s not. The switch parameter is inherently never dependent on a value to be included with it. It doesn’t work that way. The PowerShell engine will see -Confirm and make it $true, long before it even sees the $false value. The colon and value being attached to the Confirm parameter ensures the parser knows you want it to be of that specific value.

AWS UserData Multiple Run Framework Part II

My first post on the topic of using AWS UserData (via CloudFormation and PowerShell), was posted recently (http://tommymaynard.com/aws-userdata-multiple-run-framework-2017). While it didn’t light up Twitter and Facebook, where it was shared, there was still some value to it for me, and at least one other person. Therefore, I’ve decided to do a part two. This is one of these using-PowerShell-to-create-PowerShell posts. I just love these.

Quick recap on the first post. I use the below PowerShell function in all the UserData sections of all my CloudFormation documents (when building Windows systems). Additionally, I also include an If-ElseIf language construct to do specific actions against an EC2 instance, as it’s coming online. The best part here, is that I can do instance restarts between configuration steps. While I’ll include the full example (actually put together), from Part I below, it might be best to read that first post (link above).

Function Set-SystemForNextRun {
    Param (
        [string]$PassFile,
        [switch]$UserData,
        [switch]$Restart
    )
    If ($PassFile) {
        [System.Void](New-Item -Path "$env:SystemDrive\passfile$PassFile.txt" -ItemType File)
    }
    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
    }
}

If (-Not(Test-Path -Path "$env:SystemDrive\passfile1.txt")) {
 
    # Place code here (1).
 
    # Invoke Set-SystemForNextRun function.
    Set-SystemForNextRun -PassFile '1' -UserData -Restart
 
} ElseIf (-Not(Test-Path -Path "$env:SystemDrive\passfile2.txt")) {
 
    # Place code here (2).
 
    # Invoke Set-SystemForNextRun function.
    Set-SystemForNextRun -PassFile '2' -UserData -Restart
 
} ElseIf (-Not(Test-Path -Path "$env:SystemDrive\passfile3.txt")) {
 
    # Place code here (3).
 
    # Invoke Set-SystemForNextRun function.
    Set-SystemForNextRun -PassFile '3' -UserData -Restart
 
} ElseIf (-Not(Test-Path -Path "$env:SystemDrive\passfile4.txt")) {
 
    # Place code here (4).
 
    # Invoke Set-SystemForNextRun function.
    Set-SystemForNextRun -PassFile '4'
 
}

Here’s the thing, the above example is going to restart my computer three times: after the If portion, after the first ElseIf, and after the second ElseIf portion. These three restarts effectively give me four areas in which to configure an EC2 instances in sequence and again, between restarts.

There’s no way for me to predict how many code runs you or I are going to need with each new project, and whether or not we’ll want to enable UserData after the final code run, or even if we’ll need a final, final restart. Therefore, I’ve written a PowerShell function to help with this process. Why manually edit this If-ElseIf statement if you don’t have to, right? With this newer function, you can decide how many code runs you need and whether you need to enable UserData or issue a restart after your final code section executes. Here’s a few examples with the full function, New-AWSMultiRunTemplate, at the end of today’s post.

In this first example, we’ll just run the function with its default settings. This will create the Set-SystemForNextRun function and an If-ElseIf statement (with two places to configure the instance, and a single restart in between them. If this is what you needed, you’d take the output of the New-AWSMultiRunTemplate function invocation and paste it into your AWS CloudFormation’s UserData section, between an open and close PowerShell tag: <powershell> and </powershell>.

PS > New-AWSMultiRunTemplate
Function Set-SystemForNextRun {
    Param (
        [string]$PassFile,
        [switch]$UserData,
        [switch]$Restart
    )
    If ($PassFile) {
        [System.Void](New-Item -Path "$env:SystemDrive\passfile$PassFile.txt" -ItemType File)
    }
    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
    }
}

If (-Not(Test-Path -Path "$env:SystemDrive\passfile1.txt")) {

    # Place code here (1).

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

} ElseIf (-Not(Test-Path -Path "$env:SystemDrive\passfile2.txt")) {

    # Place code here (2).

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

}

This next run will create a single If statement. I suppose if you’re only going to do a single configuration pass, that you don’t need this function at all. Again, I said I use it for all my CloudFormation templates. Say you edit your CloudFormation document later and add more configuration and restarts, you’ll already have most of what you need included in your CloudFormation document.

PS > New-AWSMultiRunTemplate -CodeSectionCount 1
Function Set-SystemForNextRun {
    Param (
        [string]$PassFile,
        [switch]$UserData,
        [switch]$Restart
    )
    If ($PassFile) {
        [System.Void](New-Item -Path "$env:SystemDrive\passfile$PassFile.txt" -ItemType File)
    }
    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
    }
}

If (-Not(Test-Path -Path "$env:SystemDrive\passfile1.txt")) {

    # Place code here (1).

    # Invoke Set-SystemForNextRun function.
    Set-SystemForNextRun -PassFile '1'

}

Now, let’s get crazy! We’re going to add a bunch of ElseIf statements in this example. Additionally, we’re going to highlight a couple other features. There are parameters to use if you wish to include a final UserData reset, and a final restart. Take a look at the below parameters used with the New-AWSMultiRunTemplate.

PS > New-AWSMultiRunTemplate -CodeSectionCount 6 -EnableUserData All -EnableRestart All
Function Set-SystemForNextRun {
    Param (
        [string]$PassFile,
        [switch]$UserData,
        [switch]$Restart
    )
    If ($PassFile) {
        [System.Void](New-Item -Path "$env:SystemDrive\passfile$PassFile.txt" -ItemType File)
    }
    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
    }
}

If (-Not(Test-Path -Path "$env:SystemDrive\passfile1.txt")) {

    # Place code here (1).

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

} ElseIf (-Not(Test-Path -Path "$env:SystemDrive\passfile2.txt")) {

    # Place code here (2).

    # Invoke Set-SystemForNextRun function.
    Set-SystemForNextRun -PassFile '2' -UserData -Restart

} ElseIf (-Not(Test-Path -Path "$env:SystemDrive\passfile3.txt")) {

    # Place code here (3).

    # Invoke Set-SystemForNextRun function.
    Set-SystemForNextRun -PassFile '3' -UserData -Restart

} ElseIf (-Not(Test-Path -Path "$env:SystemDrive\passfile4.txt")) {

    # Place code here (4).

    # Invoke Set-SystemForNextRun function.
    Set-SystemForNextRun -PassFile '4' -UserData -Restart

} ElseIf (-Not(Test-Path -Path "$env:SystemDrive\passfile5.txt")) {

    # Place code here (5).

    # Invoke Set-SystemForNextRun function.
    Set-SystemForNextRun -PassFile '5' -UserData -Restart

} ElseIf (-Not(Test-Path -Path "$env:SystemDrive\passfile6.txt")) {

    # Place code here (6).

    # Invoke Set-SystemForNextRun function.
    Set-SystemForNextRun -PassFile '6' -UserData -Restart

}

Notice that in the final ElseIf above, we have the UserData and Restart parameters. These were added by including the EnableUserData and EnableRestart parameters, and All as the parameters’ value. By default, they’re both set to the AllButLast values. And that it’s! Here’s the New-AWSMultiRunTemplate function, in case it’s something you might find helpful! Enjoy!!

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&amp;gt;&amp;gt; 
$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]`$PassFile,
        [switch]`$UserData,
        [switch]`$Restart
    )
    If (`$PassFile) {
        [System.Void](New-Item -Path "`$env:SystemDrive\passfile`$PassFile.txt" -ItemType File)
    }
    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
    }
}


"@
    } # 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(Test-Path -Path "`$env:SystemDrive\passfile$_.txt")) {
    
    # Place code here ($_).

    # Invoke Set-SystemForNextRun function.
    Set-SystemForNextRun -PassFile '$_' $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 AWS mulitple run code with $CodeSectionCount code 
section(s)."
        "$TemplateFunction$TemplateIfElseIf"
    } # End End.
} # End New-AWSMultiRunTemplate function.

 

AWS UserData Multiple Run Framework

In AWS, we can utilize the UserData section in EC2 to run PowerShell against our EC2 instances at launch. I’ve said it before; I love this option. As someone that speaks PowerShell with what likely amounts to first language fluency, there’s so much I do to automate my machine builds with CloudFormation, UserData, and PowerShell.

I’ve begun to have a need to do various pieces of automation at different times. This is to say I need to have multiple instance restarts, as an instance is coming online, in order to separate different pieces of configuration and installation. You’ll figure out when you need that, too. And, when you do, you can use the what I’ve dubbed the “multiple run framework for AWS.” But really, you call it want you want. That hardly matters.

We have to remember, that by default, UserData only runs once. It’s when the EC2 instance launches for the first time. In the below example, we’re going to do three restarts and four separate code runs.

Our UserData section first needs to add a function to memory. I’ve called it Set-SystemForNextRun and its purpose is to (1) create what I call a “passfile” to help indicate where we are in the automation process, (2) enable UserData to run the next time the service is restarted (this happens at instance restart, obviously), and (3) restart the EC2 instance. Let’s have a look. It’s three parameters and three If statements; simple stuff.

Function Set-SystemForNextRun {
    Param (
        [string]$PassFile,
        [switch]$UserData,
        [switch]$Restart
    )
    If ($PassFile) {
        [System.Void](New-Item -Path "$env:SystemDrive\passfile$PassFile.txt" -ItemType File)
    }
    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
    }
}

This above function accepts three parameters: PassFile, UserData, and Restart. PassFile accepts a string value. You’ll see how this works in the upcoming If-ElseIf example. UserData and Restart are switch parameters. If they’re included when the function is invoked, they’re True ($true), and if they’re not included, they’re False ($false).

Each of the three parameters has its own If statement within the Set-SystemforNextRun function. If PassFile is included, it creates a text file called C:\passfile<ValuePassedIn>.txt. If UserData is included, it resets UserData to enabled (it effectively, checks the check box in the Ec2Config GUI). If Restart is included, it restarts the instance, right then and there.

Now let’s take a look at the If-ElseIf statement that completes four code runs and three restarts. We’ll discuss it further below, but before we do, a little reminder. Our CloudFormation UserData PowerShell is going to contain the above Set-SystemForNextRun function, and something like you’ll see below, after you’ve edited it for your needs.

If (-Not(Test-Path -Path "$env:SystemDrive\passfile1.txt")) {

    # Place code here (1).

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

} ElseIf (-Not(Test-Path -Path "$env:SystemDrive\passfile2.txt")) {

    # Place code here (2).

    # Invoke Set-SystemForNextRun function.
    Set-SystemForNextRun -PassFile '2' -UserData -Restart

} ElseIf (-Not(Test-Path -Path "$env:SystemDrive\passfile3.txt")) {

    # Place code here (3).

    # Invoke Set-SystemForNextRun function.
    Set-SystemForNextRun -PassFile '3' -UserData -Restart

} ElseIf (-Not(Test-Path -Path "$env:SystemDrive\passfile4.txt")) {

    # Place code here (4).

    # Invoke Set-SystemForNextRun function.
    Set-SystemForNextRun -PassFile '4'

}

In line 1, we test whether or not the file C:\passfile1.txt exists. If it doesn’t exist, we run the code in the If portion. This will run whatever PowerShell we add to that section. Then it’ll pass 1 to the Set-SystemForNextRun function to have C:\passfile01.txt created. Additionally, because the UserData and Restart parameters are included, it’ll reset UserData to enabled, and restart the EC2 instance. Because the C:\passfile1.txt file now exists, the next time the UserData runs, it’ll skip the If portion and evaluate the first ElseIf statement.

This ElseIf statement determines whether or not the C:\passfile2.txt file exists, or not. If it doesn’t, and it won’t after the first restart, then the code in this ElseIf will run. When it’s done, it’ll create the passfile2.txt file, reset UserData, and restart the instance. It’ll do this for second ElseIf (third code run), and the final ElseIf (fourth code run), as well. Notice that the final invocation of the Set-SystemForNextRun function doesn’t enable UserData or Restart the instance. Be sure to add those if you need either completed after the final ElseIf completes.

And that’s it. At this point in time, I always use my Set-SystemForNextRun function and a properly written If-ElseIf statement to separate the configuration and installation around the necessary amount of instance restarts. In closing, keep in mind that deleting those pass files from the root of the C:\ drive is not something you’ll likely want to do. In time, I may do a rewrite that stores entries in the Registry perhaps, so there’s less probability that one of these files might be removed by someone.

Either way, I hope this is helpful for someone! If you’re in this space — AWS, CloudFormation, UserData, and PowerShell — then chances are good that at some point you’re going to want to restart an instance, and then continue to configure it.

Linux Prompt on Windows – Part V

The last time I wrote about my Linux prompt, we were on post IV. Now it’s V, and all because I’m tired of not knowing if I’m in the debugger or not. The standard PowerShell prompt, when in the debugger, will add [DBG]: to the beginning of the prompt and an extra right angle bracket to the end. Therefore, the standard PowerShell ends up looking like it does toward the bottom of this first example.

PS C:\Users\tommymaynard> Set-PSBreakpoint -Script .\Desktop\NewScript.ps1 -Line 7
ID Script                Line Command               Variable             Action
-- ------                ---- -------               --------             ------
0 NewScript.ps1            7

PS C:\Users\tommymaynard> .\Desktop\NewScript.ps1
[DBG]: PS C:\Users\tommymaynard>> q
PS C:\Users\tommymaynard>

I’ll include my entire prompt at the end of today’s post, but before we do that, let’s focus on the new part. It’s going to add these same two things to the prompt, when I’m debugging a script. If the path, Variable:/PSDebugContext exists, we can safety assume we’re in the debugger. Therefore, when we are, we’ll assign two new variables as $DebugStart and $DebugEnd.

If (Test-Path -Path Variable:/PSDebugContext) {
    $DebugStart = '[DBG]: '
    $DebugEnd = ']'
}

Again, the above If statement is stuffed between a bunch of other PowerShell that makes up the entire prompt. Before we get there, here’s an example of what my prompt looks like now when we are, and aren’t in the debugger.

[tommymaynard@server01 c/~]$ .\Desktop\NewScript.ps1
[DBG]: [tommymaynard@server01 c/~]]$ q
[tommymaynard@server01 c/~]$ 

Excellent! Now I can continue to use my own prompt function, and know when I’m in the debugger. All this, without hitting an error to remind me. In the full prompt below, we also update the WindowTitle to reflect when we’re in the debugger, too.

# Create Linux prompt.
Function Prompt {
    (Get-PSProvider -PSProvider FileSystem).Home = $env:USERPROFILE

    # Determine if Admin and set Symbol variable.
    If ([bool](([System.Security.Principal.WindowsIdentity]::GetCurrent()).Groups -match 'S-1-5-32-544')) {
        $Symbol = '#'
    } Else {
        $Symbol = '$'
    }
	 
    # Write Path to Location Variable as /.../...
    If ($PWD.Path -eq $env:USERPROFILE) {
        $Location = '/~'
    } ElseIf ($PWD.Path -like "*$env:USERPROFILE*") {
        $Location = "/$($PWD.Path -replace ($env:USERPROFILE -replace '\\','\\'),'~' -replace '\\','/')"
    } Else {
        $Location = "$(($PWD.Path -replace '\\','/' -split ':')[-1])"
    }

    # Determine Host for WindowTitle.
    Switch ($Host.Name) {
        'ConsoleHost' {$HostName = 'consolehost'; break}
        'Windows PowerShell ISE Host' {$HostName = 'ise'; break}
        default {}
    }

    # Create and write Prompt; Write WindowTitle.
    $UserComputer = "$($env:USERNAME.ToLower())@$($env:COMPUTERNAME.ToLower())" 
    $Location = "$((Get-Location).Drive.Name.ToLower())$Location"

    # Check if in the debugger.
    If (Test-Path -Path Variable:/PSDebugContext) {
        $DebugStart = '[DBG]: '
        $DebugEnd = ']'
    }

    # Actual prompt and title.
    $Host.UI.RawUI.WindowTitle = "$HostName`: $DebugStart[$UserComputer $Location]$DebugEnd$Symbol"
    "$DebugStart[$UserComputer $Location]$DebugEnd$Symbol "
}

Break From a Nested Loop

I’m building a new version of my “Multi-Level Menu System with a Back Option.” Here’s the URL from the May 2016 post: http://tommymaynard.com/script-sharing-multi-level-menu-system-with-back-option-2016. This is a post where I wrote about creating a text based nested menu system. It was neat, but the nested Switch statements got a little confusing, and so it was never used by me, or potentially anyone else. I have no idea.

What I do know, is that a few times this past weekend I was able to work on a redesign for this menu system. The problem was this: From the main menu, you can press Q to quit. In the nested menus you either choose an option (1 through whatever), or press B to go back a menu. This means that if you’re three menus deep, you have to press B until you’re back at the main menu in order to press Q to quit. You can’t quit from a nested menu. Well, you couldn’t before this weekend, anyway.

We won’t go into the menu system for now, but I do want to leave an example of how to break out of nested loops. I seriously, learned something I’ve yet to ever see, and so maybe this will be a first for you, as well. We’ve all seen, and likely used break. You can read more at about_break using Get-Help: Get-Help -Name about_Break -ShowWindow.

In this first example, we’ll write the string “This is a test.” until we stop the loop’s execution. There’s nothing about this loop that’s ever going to make it stop without our help.

While ($true) {
    'This is a test.'
}

'This is a test.'
'This is a test.'
'This is a test.'
...

In this next example, we’ll immediately break out of the While loop by using the break statement. Prior to breaking out, we’ll write “This is a test.” to the host program, but this time, it’ll only be written once and then the execution will end.

While ($true) {
    'This is a test.'
    break
}
'This is a test.'

Let’s start our next example by nesting a While loop, inside of a While loop. In this example, we’ll write “Outer While loop” once, and then continually write “Nested While loop” until we manually end the execution. We can’t get back to the outer While loop, when we’re forever stuck in the inner While loop.

While ($true) {
    'Outer While loop'

    While ($true) {
        'Nested While loop'
    }
}
'Outer While loop'
'Nested While loop'
'Nested While loop'
'Nested While loop'
...

This next example includes a break statement inside the nested While loop. This means we’ll write “Outer While loop” and “Nested While loop” over and over, forever. We’ll at least until we stop the execution. In this example, we can get back to the outer While loop.

While ($true) {
    'Outer While loop'

    While ($true) {
        'Nested While loop'
        break
    }
}
'Outer While loop'
'Nested While loop'
'Outer While loop'
'Nested While loop'
'Outer While loop'
'Nested While loop'
...

Our final example, will include the word outer, after the break statement. In this example, we’ll execute the outer While loop, execute the inner While loop, and then break out of both of the While loops. Yes both, from inside the inner loop.

I didn’t even know this was possible before the weekend. My nested menu system is absolutely going to need this! Now, I can allow my users to quit, no matter how deep their level nestation — yes, I totally just made up that word. Enjoy, and maybe it’s helpful for you one day!

While ($true) {
    'Outer While loop'

    While ($true) {
        'Nested While loop'
        break outer
    }
}
'Outer While loop'
'Nested While loop'

A Function for my Functions

I’m in the process of working on a new advanced function template for work. It’ll be the 2.0 version of this: http://tommymaynard.com/function-logging-via-write-verbose-2016. The problem I have with the current version of the template is that I could never only log to a file, without writing to the host program, too. In the end, I want a way to do one of four different things when I run a function written using the template. They are (1) not log anything, (2) log to the screen only, (3) log to a file only (this is the addition I’m after), or (4) log to both the screen and a file simultaneously. This is nearing completion, but one of the downsides to a well thought out and robust advanced function template is that it’s getting, really long. I honestly believe I’m at over 100 lines now (the 1.x versions are in the 70ish line territory).

So, I’m sitting around and thinking of a way to disguise the template, or add my code to it when it’s done, and so I wrote the following pieces of PowerShell. No saying that I’ll ever use this, but it seemed worthy enough to share here. I’m out to automatically add my code to the template, so I don’t have to code around the template’s standard code that allows for the logging.

This first example begins the process of creating a new function named New-FunctionFromTemplate. It’s set to accept three parameters: Begin, Process, and End. For those writing advanced functions, this should be easily recognizable as to why. It’s going to stuff whatever values are supplied to these parameters into these respective sections in the function template, that it displays when it’s done executing. You write the code, and it’ll place that code into the function template, via the New-FunctionFromTemplate function.

Function New-FunctionFromTemplate {
    Param(
        [string]$Begin,
        [string]$Process,
        [string]$End
    )
...
}

Next, I’ve included the remainder of this function. It’s a here-string that includes the function template design and layout, with a place for each variable within it. These variables will be replaced with whatever we send into the New-FunctionFromTemplate function when it’s invoked.

Function New-FunctionFromTemplate {
    Param(
        [string]$Begin,
        [string]$Process,
        [string]$End
    )   

    @"
Function ___-________ {
    [CmdletBinding()]
    Param (
    )

    Begin {
        $Begin
    } # End Begin.

    Process {
        $Process
    } # End Process.

    End {
        $End
    } # End End.
} # End Function: ___-________.
"@
}

Now that we’ve defined our function, let’s use it. As a bit of a shortcut, and as a way to make things a bit more readable, we’ll create a parameter hash table and splat it to the New-FunctionFromTemplate function. The below example could’ve been written as New-FunctionFromTemplate -Begin ‘This is in the Begin block.’ -Process ‘This is in the Process block.’ … etc., but I’m opted to not to that, to make things a bit easier to read and comprehend.

$Params1 = @{
    Begin = 'This is in the Begin block.'
    Process = 'This is in the Process block.'
    End = 'This is in the End block.'
}
New-FunctionFromTemplate @Params1

Below is the output the above commands create.

Function ___-________ {
    [CmdletBinding()]
    Param (
    )

    Begin {
        This is in the Begin block.
    } # End Begin.

    Process {
        This is in the Process block.
    } # End Process.

    End {
        This is in the End block.
    } # End End.
} # End Function ___-________.

This thing is, someone’s not typically only going to add a single string — a single sentence, if you will — inside a Begin, Process, or End block. They’re much more likely to add various language constructs and logic, and comments. Here’s a second example to help show how we would go about adding more than just a single string, using a here-string for two of the three blocks.

$Params2 = @{
    Begin = @'
If ($true) {
            'It is true.'
        } Else {
            'It is false.'
        }
'@
    Process = @'
'This is in the Process block.'
'@
    End = @'
If ($false) {
            'It is true.'
        } Else {
            'It is false.'
        }
'@
}
New-FunctionFromTemplate @Params2

When the above code is run, it produces the below output. It includes both our template structure, as well as, the code we want inside each of the blocks. In case you didn’t catch it right away, there’s a bit of a caveat. The first line is right up against the left margin. Even though, it’ll drop everything into it’s proper place — a single tab to the right of the beginning of each block. After that first line, you have to be the one to monitor your code placement, so that when it’s combined with the template, all the indentations line as expected.

Function ___-________ {
    [CmdletBinding()]
    Param (
    )

    Begin {
        If ($true) {
            'It is true.'
        } Else {
            'It is false.'
        }
    } # End Begin.

    Process {
        'This is in the Process block.'
    } # End Process.

    End {
        If ($false) {
            'It is true.'
        } Else {
            'It is false.'
        }
    } # End End.
} # End Function: ___-________.

And, that’s it. While I put this together, I’ve yet to implement it and truly code separately from my current template. We’ll see what the future holds for me, but at least I know I have this option, if I decide it’s really time to use it. Enjoy your Thursday!

PowerShell Code and AWS CloudFormation UserData

Note: This post was written well over a month ago, but was never posted, due to some issues I was seeing in AWS GovCloud. It works 100% of the time now, in both GovCloud and non-GovCloud AWS. That said, if you’re using Read-S3Object in GovCloud, you’re going to need to include the Region parameter name and value.

As I spend more and more time with AWS, I end up back at PowerShell. If I haven’t said it yet, thank you Amazon Web Services, for writing us a PowerShell module.

In the last month, or two, I’ve been getting into the CloudFormation template business. I love the whole UserData option we have — injecting PowerShell code into an EC2 instance during its initialization, and love, that while we can do it in the AWS Management Console, we can do it with CloudFormation (CFN) too. In the last few months, I’ve decided to do things a bit differently. Instead of dropping large amounts of PowerShell code inside my UserData property in the CFN template, I decided to use Read-S3Object to copy PowerShell modules to EC2 instances, and then just issue calls to the functions in the remainder of the CFN UserData. In one instance, I went from 200+ lines of PowerShell in the CFN template, to just a few.

To test, I needed to verify if I could get a module folder and file into the proper place on the instance and be able to use the module’s function(s) immediately, without any need to end one PowerShell session, and start a new one. I suspected this would work just fine, but it needed to be seen.

Here’s how the testing went: On my Desktop, I have a folder called MyModule. Inside the folder I have a file called MyModule.psm1. If you haven’t seen it before, this file extension indicates the file is a PowerShell module file. The contents of the file, are as follows:

Function Get-A {
    'A'
}

Function Get-B {
    'B'
}

Function Get-C {
    'C'
}

The file contents indicate that the module contains three functions: Get-A, Get-B, and Get-C. In the next example, we can see that the Desktop folder isn’t a place where a module file and folder can exist, where we can expect that the modules will be automatically loaded into the PowerShell session. PowerShell isn’t aware of this module on its own, as can be seen below.

PS > Get-A
Get-A : The term 'Get-A' 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
+ Get-A
+ ~~~~~
    + CategoryInfo          : ObjectNotFound: (Get-A:String) [], CommandNotFoundException
    + FullyQualifiedErrorId : CommandNotFoundException

PS > Get-B
Get-B : The term 'Get-B' 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
+ Get-B
+ ~~~~~
    + CategoryInfo          : ObjectNotFound: (Get-B:String) [], CommandNotFoundException
    + FullyQualifiedErrorId : CommandNotFoundException

PS > Get-C
Get-C : The term 'Get-C' 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
+ Get-C
+ ~~~~~
    + CategoryInfo          : ObjectNotFound: (Get-C:String) [], CommandNotFoundException
    + FullyQualifiedErrorId : CommandNotFoundException

While I could tell PowerShell to look on my desktop, what I wanted to do is have my CFN template copy the module folder out of S3 and place it on the instances, in a preferred and proper location: “C:\Program Files\WindowsPowerShell\Modules.” This is a location that PowerShell checks for modules automatically, and loads them the moment a contained function, or cmdlet, from the module is requested. My example uses a different path, but PowerShell will check here automatically, as well. As a part of this testing, we’re pretending that the movement from my desktop is close enough to the movement from S3 to an EC2 instance. I’ll obviously test this more with AWS.

PS > Move-Item -Path .\Desktop\MyModule\ -Destination C:\Users\tommymaynard\Documents\WindowsPowerShell\Modules\
PS > Get-A
A
PS > Get-B
B
PS > Get-C
C

Without the need to open a new PowerShell session, I absolutely could use the functions in my module, the moment the module was moved from the Desktop into a folder PowerShell looks at by default. Speaking of those locations, you can view them by returning the value in the $env:PSModulePath environmental variable. Use $env:PSModulePath -split ‘;’ to make it easier to read.

Well, it looks like I was right. I can simply drop those modules folders on the EC2 instance, into “C:\Program Files\WindowsPowerShell\Modules,” just before they’re used with no need for anything more than the current PowerShell session that’s moving them into place.

Update: After this on-my-own-computer test, I took it to AWS. It works, and now it’s the only way I use my CFN template UserData. I write my my function(s), house them in a PowerShell module(s), copy them to S3, and finally use my CFN UserData to copy them to the EC2 instance. When that’s complete, I can call the contained function(s) without any hesitation, or additional work. It wasn’t necessary, but I added sleep commands between the function invocations. Here’s a quick, modified example you might find in the UserData of one of my CloudFormation templates.

      UserData:
        Fn::Base64:
          !Sub |
          <powershell>
            # Download PowerShell Modules from S3.
            $Params = @{
              BucketName = 'windows'
              Keyprefix = 'WindowsPowerShell/Modules/ProjectVII/'
              Folder = "$env:ProgramFiles\WindowsPowerShell\Modules"
            }
            Read-S3Object @Params | Out-Null

            # Invoke function(s).
            Set-TimeZone -Verbose -Log
            Start-Sleep -Seconds 15

            Add-EncryptionType -Verbose -Log
            Start-Sleep -Seconds 15

            Install-ProjectVII -Verbose -Log
            Start-Sleep -Seconds 15
            </powershell>

Use PowerShell to Edit a CSV

I recently completed a scripting assignment for work. Yeah… it’s a part of what I do. During the process I learned something new, that I hadn’t known before. And like it normally does, this leaves me in a position to share it with the Internet, as well as help solidify it in my own mind. And that’s, why I’m writing today.

I thought my recent assignment had a need to edit an existing CSV file on the fly. It turns out it wasn’t necessary for the project, but just maybe it will be for another one, or maybe even one that’s sitting in front of you right now. So, how do you do that? How do you edit an existing CSV file?

Let’s begin with a simple, yet worthy CSV file as an example. In the below CSV data, we have four columns, fields, or properties — whatever you want to call them at this point. They are Name, Status, BatchName, and SentNotification. The idea here — or what I thought it was in my recent assignment, at first — was to notify by email the users that were in a Synced state and then modify the SentNotification field so that it said True, instead of FALSE. Take a look at our example data.

Name,Status,BatchName,SentNotification
landrews,Other,MigrationService:FirstBatch-to-O365,FALSE
lpope,Synced,MigrationService:FirstBatch-to-O365,FALSE
ljohnson,Other,MigrationService:FirstBatch-to-O365,FALSE
mlindsay,Other,MigrationService:FirstBatch-to-O365,FALSE
rperkins,Synced,MigrationService:FirstBatch-to-O365,FALSE
dstevenson,Other,MigrationService:FirstBatch-to-O365,FALSE
jbradford,Other,MigrationService:FirstBatch-to-O365,FALSE
jsmith,Other,MigrationService:FirstBatch-to-O365,FALSE
mdavidson,Synced,MigrationService:FirstBatch-to-O365,FALSE
bclark,Synced,MigrationService:FirstBatch-to-O365,FALSE

Let’s first begin by using Import-Csv and Format-Table to view our data.

Import-Csv -Path '.\Desktop\UCSVTestFile.csv' | Format-Table -AutoSize

Name       Status BatchName                           SentNotification
----       ------ ---------                           ----------------
landrews   Other  MigrationService:FirstBatch-to-O365 FALSE           
lpope      Synced MigrationService:FirstBatch-to-O365 FALSE           
ljohnson   Other  MigrationService:FirstBatch-to-O365 FALSE           
mlindsay   Other  MigrationService:FirstBatch-to-O365 FALSE           
rperkins   Synced MigrationService:FirstBatch-to-O365 FALSE           
dstevenson Other  MigrationService:FirstBatch-to-O365 FALSE           
jbradford  Other  MigrationService:FirstBatch-to-O365 FALSE           
jsmith     Other  MigrationService:FirstBatch-to-O365 FALSE           
mdavidson  Synced MigrationService:FirstBatch-to-O365 FALSE           
bclark     Synced MigrationService:FirstBatch-to-O365 FALSE

Now what we need to do, is modify the content, as it’s being imported. Let’s start first, however, by piping our Import-Csv cmdlet to ForEach-Object and returning each object (line).

Import-Csv -Path '.\Desktop\UCSVTestFile.csv' | ForEach-Object {
    $_
}

Name       Status BatchName                           SentNotification
----       ------ ---------                           ----------------
landrews   Other  MigrationService:FirstBatch-to-O365 FALSE           
lpope      Synced MigrationService:FirstBatch-to-O365 FALSE           
ljohnson   Other  MigrationService:FirstBatch-to-O365 FALSE           
mlindsay   Other  MigrationService:FirstBatch-to-O365 FALSE           
rperkins   Synced MigrationService:FirstBatch-to-O365 FALSE           
dstevenson Other  MigrationService:FirstBatch-to-O365 FALSE           
jbradford  Other  MigrationService:FirstBatch-to-O365 FALSE           
jsmith     Other  MigrationService:FirstBatch-to-O365 FALSE           
mdavidson  Synced MigrationService:FirstBatch-to-O365 FALSE           
bclark     Synced MigrationService:FirstBatch-to-O365 FALSE

Hey, look at that. It’s the same thing. The $_ variable represents the current object, or the current row, if it helps to think about it that way — in the pipeline. Let’s add an If statement inside our ForEach-Object loop, and get the results we’re after. Remember, if a user has a Synced status, we want to change their SentNotification property to $true, and perhaps notify them, had this been more than just an example.

Import-Csv -Path '.\Desktop\UCSVTestFile.csv' | ForEach-Object {
    If ($_.Status -eq 'Synced' -and $_.SentNotification -eq $false) {
        $_.SentNotification = $true
    }
    $_
} | Format-Table -AutoSize

Name       Status BatchName                           SentNotification
----       ------ ---------                           ----------------
landrews   Other  MigrationService:FirstBatch-to-O365 FALSE           
lpope      Synced MigrationService:FirstBatch-to-O365 True            
ljohnson   Other  MigrationService:FirstBatch-to-O365 FALSE           
mlindsay   Other  MigrationService:FirstBatch-to-O365 FALSE           
rperkins   Synced MigrationService:FirstBatch-to-O365 True            
dstevenson Other  MigrationService:FirstBatch-to-O365 FALSE           
jbradford  Other  MigrationService:FirstBatch-to-O365 FALSE           
jsmith     Other  MigrationService:FirstBatch-to-O365 FALSE           
mdavidson  Synced MigrationService:FirstBatch-to-O365 True            
bclark     Synced MigrationService:FirstBatch-to-O365 True

In the above example, we use an If statement to check the values of two properties. If Status is Synced and SentNotification is $false, we’ll change SentNotification to $true. You can see that this worked. But what now? You see, the file from which we did our import is still the same. In order to update that file, we have a bit more work to do.

I wish I could say pipe directly back to the file; however, that doesn’t work. The file ends up being blank. It makes sense is doesn’t work though, as we’re literally reading each object — each row — and then trying to write back to the file in the same pipeline. Something is bound to go wrong, and it does. So, don’t do what’s in the below example, unless your goal is to fail at this assignment and wipe out your data. If that’s what you’re after, then by all means, have at it.

Import-Csv -Path '.\Desktop\UCSVTestFile.csv' | ForEach-Object {
    If ($_.Status -eq 'Synced' -and $_.SentNotification -eq $false) {
        $_.SentNotification = $true
    }
    $_
} | Export-Csv -Path '.\Desktop\UCSVTestFile.csv' -NoTypeInformation

What we need to do instead, is Export to a file with a different name, so that when we’re done, both files exist at the same time. Then, we remove the original file and rename the new one with the old one’s name. Here’s the entire example; take a look. And then after that, enjoy the weekend. Oh wait, tomorrow is only Friday. I keep thinking it’s the weekend, because I’m home tomorrow to deal with 1,000 square feet of sod. If only PowerShell could lay the sod for me.

Import-Csv -Path '.\Desktop\UCSVTestFile.csv' | ForEach-Object {
    If ($_.Status -eq 'Synced' -and $_.SentNotification -eq $false) {
        $_.SentNotification = $true
    }
    $_
} | Export-Csv -Path '.\Desktop\UCSVTestFile-temp.csv' -NoTypeInformation
Remove-Item -Path '.\Desktop\UCSVTestFile.csv'
Rename-Item -Path '.\Desktop\UCSVTestFile-temp.csv' -NewName 'UCSVTestFile.csv'