Twitter Reply – Finding Parameters that Include a Default Value

Adam Bertram, an active PowerShell MVP, wrote on Twitter about the need to determine which parameters have default values. I can’t resist a challenge, and so I very quickly wrote out the function below. No guarantees, but maybe it’ll help.

Update: It turns out that the function that Adam was working with, didn’t include help. Shame. Even so, this small script may assist someone to pull back the parameters that have default values.

Get-Help Get-Service |
    Select-Object -ExpandProperty parameters |
    Select-Object -ExpandProperty parameter | 

    Foreach {
        If ($_.DefaultValue -ne '') {
            $Object = [pscustomobject]@{
                Name = $_.Name
                Default =$_.DefaultValue
            }
        Write-Output -InputObject $Object
        }
    }

Save External Dynamic IP to Dropbox

There are times when I’m away from the house, where I need to be able to reach my home computer via Remote Desktop. Because of this occasional need, I have my home router set up to allow this external connection. To do this required a static (internal) IP assignment for my home computer (I actually use a DHCP reservation), my router listening on port 3389 on the outside IP, and port forwarding that routes this external traffic to the home computer’s internal IP address. I used to connect by name, but the application I was using to sync my IP with my hostname, doesn’t seem to be working consistently. Therefore, I’ve been using my fallback option a lot lately: PowerShell and Dropbox.

Here’s what happens: I have a scheduled task on my home computer that runs a PowerShell script at 12 a.m., 6 a.m., 12 p.m, and 6 p.m. each day. The purpose of the script is to get my current, outside IP and write it, as well as the date and time, to a text file in Dropbox. That file is then snyced to my other computer and phone. It’s simple.

In line one, below, we create a variable, $UseableDate, to hold the string I use for the date and time. For today’s date, it might look something like this: D2015-08-19_T09-02-43-PM. This has long been my preferred way to store the date and time, especially when used in file names, as it will keep everything in a sort able order by year, month, and then day, and doesn’t include any invalid characters when used in a file path.

$UseableDate = Get-Date -Format 'Dyyyy-MM-dd_Thh-mm-ss-tt'

Next we need to collect the outside IP address. This is an important distinction — I don’t want my internal, NAT’d IP address. Line two, below, which checks in with dyndns.com, was borrowed from Aman Dhally. I’ve included a couple other options that can be used to populate the same $IPAddress variable (note: ifconfig.me has always seemed to be slower than the rest). Notice that using something other than dyndns will not require using any Regex, just some trimming to remove some white space surrounding the IP address.

$IPAddress = (Invoke-WebRequest -Uri ‘http://wtfismyip.com/text’).Content.Trim()
$IPAddress = (Invoke-WebRequest -Uri ‘http://ifconfig.me/ip’).Content.Trim()

$UseableDate = Get-Date -Format 'Dyyyy-MM-dd_Thh-mm-ss-tt'
$IPAddress = (Invoke-WebRequest -Uri 'http://checkip.dyndns.com').Content -replace "[^\d\.]"

The third line in the script, joins the date and time with the IP address and writes (appends) it to the file inside my Dropbox folder. Again, this is running from my home computer. Once in Dropbox at home, my work computer and phone will have the newest version of the file. In fact, it’s become a pretty good reminder that it is lunch time at work, as Dropbox pops up a Notification Area balloon that my file has been updated at 12 p.m.

$UseableDate = Get-Date -Format 'Dyyyy-MM-dd_Thh-mm-ss-tt'
$IPAddress = (Invoke-WebRequest -Uri 'http://checkip.dyndns.com').Content -replace "[^\d\.]"
"$UseableDate -- $IPaddress" | Out-File -FilePath 'C:\Users\tommymaynard\Dropbox\IP\homecomputer-IP.txt' -Append

To be as complete as possible, and save as many seconds as I can per day, I have a little function in my profile on my work computer to grab the IP address from the file. First, here’s an example of my homecomputer-IP.txt file (with hashtags taking the place of the digits):

...
D2015-08-18_T06-00-03-PM -- ##.###.##.###
D2015-08-19_T12-00-06-AM -- ##.###.##.###
D2015-08-19_T06-00-07-AM -- ##.###.##.###
D2015-08-19_T12-00-06-PM -- ##.###.##.###
D2015-08-19_T06-00-03-PM -- ##.###.##.###

The function below will extract the last IP address added to the homecomputer-IP.txt file. With that, I can add it to the Remote Desktop command line executable and get connected to the home computer from anywhere.

Function Get-HomeIP {
    $IPFile = 'C:\Users\tommymaynard\Dropbox\IP\homecomputer-IP.txt'
    (Get-Content -Path $IPFile | Select-Object -Last 1).Split(' -- ')[-1]
}
PS> Get-HomeIP
##.###.##.###
PS> mstsc.exe /v (Get-HomeIP)

That’s it — I hope this might be helpful to someone, someday. It’s saved me a time, or two now.

Update [02/09/2017]: I recently made a couple changes to the above function. The problem was that sometimes the IP wasn’t written to the file, for whatever reason. In this instance, my function ends up returning nothing. Therefore, I now sanitize the file first, so that any rows that don’t contain an IP address are excluded. Take a look.

Function Get-HomeIP {
    $IPFile = 'C:\Users\$env:USERNAME\Dropbox\IP\homecomputer-IP.txt'
    $SantizedIPs = Get-Content -Path $IPFile | Where-Object -FilterScript {$_.Length -gt 34}
    ($SantizedIPs | Select-Object -Last 1).Split(' -- ')[-1]
}

Copy Outlook Signature to Clipboard

As far as I am aware, the in-house built front end for our help desk ticketing system, doesn’t have a way to include a signature. This means that as I update and close tickets in the office, I often find myself opening a new, blank email, copying my signature, and pasting it in the notes field on the ticket. I know, I know — I’m embarrassed.

No more, am I going to consider this acceptable, especially for someone that uses PowerShell for as many things as I do: it’s. always. open. Today was the day I fixed this forever, and it took a whole two minutes.

I needed to first determine where Outlook (2013 on Windows 8.1) looks for my signature. I traced it down to C:\Users\tommymaynard\AppData\Roaming\Microsoft\Signatures. In that path there are three files named Standard — the same name used for my Signature in Outlook, when I open the Signatures and Stationary dialog. There is a .htm version, a .rtf version, and a .txt version of the signature. Simple decision: I decided I would make use of the text file.

Since I was going to use this in my profile, I didn’t include anything inside the function, but the simple command I wanted to run. Based on the function below, all I need to do is enter Get-Signature, or its alias, and my function will copy the contents of Standard.txt to my clipboard. From there, it’s a quick paste into the help desk ticketing system, and done.

Set-Alias -Name sig -Value Get-Signature
Function Get-Signature {
    $SigPath = 'C:\Users\tommymaynard\AppData\Roaming\Microsoft\Signatures\Standard.txt'
    Get-Content -Path $SigPath | Select-Object -First 4 | clip
}

If you’ve taken a look at the function, you’ll see that I only choose the first 4 lines of the signature file. This was because there was a blank line beneath the last line in my signature, that I wasn’t interested in copying (or manually removing from the file itself).

Parsing nslookup: Only Return the Resolved IP

It was late last Friday evening when a vendor and I worked though some maintenance. As a part of the work (which actually lasted well into the weekend), we needed to remove two DNS A records and replace them with CNAME records. I figured that before the change, I would return the current resolution results, so I would be able to ensure the DNS change took place, after the night’s weekend’s work.

You’re probably familiar with the standard output of nslookup.

PS> nslookup subdomain.mydomain.com
Server:  mydomain.com
Address:  10.10.10.1

Name:    subdomain.mydomain.com
Address:  10.20.30.40

These results indicate the name, subdomain.mydomain.com, resolves to an IP address of 10.20.30.40 when using the mydomain.com nameserver, at 10.10.10.1. I wanted to clean up this output, and since I had time to kill while fighting off sleep, I figured — why not? I started by piping the results of nslookup to the Select-String cmdlet looking for the string, ‘Address.’ As you can see in the example below, it returns both the IP of the server where the resolution took place, and the results. After that, there doesn’t seem to be a way to distinguish one line from the other. I needed another way to only return the results.

PS> nslookup subdomain.mydomain.com | Select-String Address

Address: 10.10.10.1
Address: 10.20.30.40

I decided to pipe the command above to the Get-Member cmdlet to see what kind of object the Select-String cmdlet returned. The property that jumped out at me right away was LineNumber — was it really going to be that easy?

   TypeName: Microsoft.PowerShell.Commands.MatchInfo

Name         MemberType Definition
----         ---------- ----------
Equals       Method     bool Equals(System.Object obj)
GetHashCode  Method     int GetHashCode()
GetType      Method     type GetType()
RelativePath Method     string RelativePath(string directory)
ToString     Method     string ToString(), string ToString(string directory)
Context      Property   Microsoft.PowerShell.Commands.MatchInfoContext Context {get;set;}
Filename     Property   string Filename {get;}
IgnoreCase   Property   bool IgnoreCase {get;set;}
Line         Property   string Line {get;set;}
LineNumber   Property   int LineNumber {get;set;}
Matches      Property   System.Text.RegularExpressions.Match[] Matches {get;set;}
Path         Property   string Path {get;set;}
Pattern      Property   string Pattern {get;set;}

Here’s the thing, the output of nslookup is always the same: this on a this line, and that on that line. This is likely why the LineNumber property looked so promising, so quickly. Now that we know we have this property, let’s try and use it. While my first line number guess worked (read, dumb luck), let’s do it wrong first. Consider again our most recent output below.

PS> nslookup subdomain.mydomain.com | Select-String Address

Address: 10.10.10.1
Address: 10.20.30.40

You might think you would want line 3 of our output, but that’s not correct. Even though Select-String has trimmed down the output, the line numbers are all still intact. Look at the very first example at the top of this post. Line 3 is blank — it’s line 5 that we want! Take a look.

PS> nslookup subdomain.mydomain.com | Select-String Address | Where-Object LineNumber -eq 5

Address: 10.20.30.40

If you want to store this as as a string, use the .ToString() method.

PS> (nslookup subdomain.mydomain.com | Select-String Address | Where-Object LineNumber -eq 5).ToString()
Address: 10.20.30.40

And, if you want to remove the word “address,” the colon, and the space, in order to only return the IP address, then you can parse the string by using the .Split() method. The example below splits the string at the space and return the last element — what’s on the right side of the space.

PS> (nslookup subdomain.mydomain.com | Select-String Address | Where-Object LineNumber -eq 5).ToString().Split(' ')[-1]
10.20.30.40

I hope this was helpful for someone. Until next time.

Active Directory User Lookup Form

Download link at bottom of post.

I had some free time recently, so I decided I would write my first, hand-coded PowerShell form using Windows Forms. It took a bit to get started and feel comfortable, but before I knew it, I was adding some useful things to my form. My form’s purpose is to lookup users in Active Directory by their user logon name (think, SamAccountName) and return a specific set of properties — Name, Distinguished Name, Mail, Title, Department, and Office Phone.

Feel free to download, and use the form if you think it may be helpful. While there are a few things I’d like to add, and change, I think it’s a solid, version 1.0 effort. There’s a screen capture of the form in action below, and the download link is just beneath that.

In closing, I wouldn’t be surprised to find out that a tool, such as this, has already been developed and made available — I didn’t bother checking for that prior to writing this tool. I just wanted to completely write my own form in PowerShell with Windows Forms, and needed an idea.

Script Sharing - PowerShell Active Directory User Lookup Form01

Download the Active Directory User Lookup Form here: https://gist.github.com/tommymaynard/b833e7fa33dd76f2484b73db58a7d281

Get Multiple Percentages of Multiple Values

Before my parents decided to trade in their truck at the dealership this week, we began to discuss the possibility of me selling it for them. Back when we started the discussion, I was thinking about possible prices for the truck, and the payout for me. Go figure, but, much like other things, I was able to incorporate Windows PowerShell into this, too.

Had this actually happened, we were going to need to determine how much I was going make in this deal. Here’s how I saw the options:

– They indicate an amount they want for the truck, and everything over that amount is mine.
– They give me a set dollar amount to sell the truck, regardless of the purchase price.
– They agree to a percentage of the final purchase price.

This last option was the one that had me flip over to my PowerShell console. You see, I often use the console as my calculator. I’m faster at typing out my math problems there, than I am using the built-in calculator in Windows.

Let’s use the possible purchase prices of $12,500, $13,000, $13,500, $14,000, and $14,500. In case you’re interested, it was a 2005 F150 SuperCrew Cab with under 70,000 miles — you read that right, less than 7,000 miles per year. What I wanted to determine was how much my parents and I would each make if I received 1%, 2.5%, 5%, 7.5%, 10%, and 12% of each of the possible purchase prices. Enter PowerShell.

The first thing I did was pipe each of the possible purchase prices to the ForEach-Object cmdlet and write their values to the screen. The results indicated that each value was properly crossing the pipeline.

12500,13000,13500,14000,14500 | ForEach-Object {
    Write-Output -Verbose $_
}
12500
13000
13500
14000
14500

Next, I created a variable for each percentage, based on a calculation. On the way into the loop, it would multiply the possible purchase price, of that iteration, by the percentage amount and store it in the proper variable. Notice that I have variables with dots (or periods, or decimals) in them, such as ${7.5Percent}. In order to use something other than a letter, number, or underscore, we need to wrap the variable in curly brackets. It’s not recommend to use variable with anything other than letters and numbers, but I thought it made sense in this case.

12500,13000,13500,14000,14500 | ForEach-Object {
    $12Percent = $_ * .12
    $10Percent = $_ * .1
    ${7.5Percent} = $_ * .075
    $5Percent = $_ * .05
    ${2.5Percent} = $_ * .025
    $1Percent = $_ * .01
}

After these variables were assigned, I created a header and footer that helped to separate the results from one another. Remember, each iteration through the loop prints these two lines of asterisks. Notice that the top row will include the current possible purchase price in the middle. The colors will also help separate the different purchase prices and the percentages.

12500,13000,13500,14000,14500 | ForEach-Object {
    $12Percent = $_ * .12
    $10Percent = $_ * .1
    ${7.5Percent} = $_ * .075
    $5Percent = $_ * .05
    ${2.5Percent} = $_ * .025
    $1Percent = $_ * .01

    Write-Host -Object "*****$_*****" -ForegroundColor Green

    Write-Host -Object '***************' -ForegroundColor Green
}

Here’s how this displayed at this point.

Get-Multiple-Percentages-of-Multiple-Values00

In this next example, you can see where included the 90% value and 10% value of each possible purchase price. To determine these two values, we subtracted our 10% amount from the full purchase price (for 90%). The second value was already determined as part of setting the variable at the beginning of the loop, so we echoed what was already in the variable.

12500,13000,13500,14000,14500 | ForEach-Object {
    $12Percent = $_ * .12
    $10Percent = $_ * .1
    ${7.5Percent} = $_ * .075
    $5Percent = $_ * .05
    ${2.5Percent} = $_ * .025
    $1Percent = $_ * .01

    Write-Host -Object "*****$_*****" -ForegroundColor Green

    Write-Host -Object "90%: $($_ - $10Percent)" -ForegroundColor Yellow
    Write-Host -Object "10%: $10Percent" -ForegroundColor Gray

    Write-Host -Object '***************' -ForegroundColor Green
}

Here’s what it looked like when the code above was run.

Get-Multiple-Percentages-of-Multiple-Values01

Here’s the final code and results.

12500,13000,13500,14000,14500 | ForEach-Object {
    $12Percent = $_ * .12
    $10Percent = $_ * .1
    ${7.5Percent} = $_ * .075
    $5Percent = $_ * .05
    ${2.5Percent} = $_ * .025
    $1Percent = $_ * .01

    Write-Host -Object "*****$_*****" -ForegroundColor Green

    Write-Host -Object "87.5%: $($_ - $12Percent)" -ForegroundColor Yellow
    Write-Host -Object "12.5%: $12Percent" -ForegroundColor Gray

    Write-Host -Object "90%: $($_ - $10Percent)" -ForegroundColor Yellow
    Write-Host -Object "10%: $10Percent" -ForegroundColor Gray

    Write-Host -Object "92.5%: $($_ - ${7.5Percent})" -ForegroundColor Yellow
    Write-Host -Object "7.5%: ${7.5Percent}" -ForegroundColor Gray

    Write-Host -Object "95%: $($_ - $5Percent)" -ForegroundColor Yellow
    Write-Host -Object "5%: $5Percent" -ForegroundColor Gray

    Write-Host -Object "97.5%: $($_ - ${2.5Percent})" -ForegroundColor Yellow
    Write-Host -Object "2.5%: ${2.5Percent}" -ForegroundColor Gray

    Write-Host -Object "99%: $($_ - $1Percent)" -ForegroundColor Yellow
    Write-Host -Object "1%: $1Percent" -ForegroundColor Gray

    Write-Host -Object '***************' -ForegroundColor Green
}

Get-Multiple-Percentages-of-Multiple-Values02

In the results, above, I’ve only included the first three possible purchase prices, to save space. While all of this could have been figured out with a calculator, there’s no possible way it could have been done as quickly — at least for someone that knows PowerShell. I didn’t bother to create an object in this instance, although it would have made sense. In my mind this wasn’t going to be reused any more than it was this one time, and so I didn’t add the additional effort.

I hope this is helpful for someone. Cheers.

Add a Dynamic Number of Asterisks

Recently, on a Windows PowerShell forum, a person wanted a script to run each time they connected to a PowerShell remote endpoint — such as a local profile does when you open the PowerShell, console host. I recommended a couple options: Create a new endpoint, and use a ScriptsToProcess .ps1 file, or modify the default, Microsoft.PowerShell endpoint, using the Set-PSSessionConfiguration cmdlet.

To use the second option I recommended, you need to add a start up script, such as the example below does. Keep in mind that you can name the .ps1 file anything you’d like. That said, I typically name these files so that I’m certain what they go with — such as, which endpoint.

PS> Set-PSSessionConfiguration -Name Microsoft.PowerShell -StartUpScript C:\Microsoft.PowerShell.StartUpScript.ps1

In order to test my updated, Microsoft.PowerShell endpoint (on my desktop computer), I decided I would add a couple informational lines of text to the script. One would indicate the “remote” computer’s name, and the other would provide the date and time. Here’s that output.

PS> Enter-PSSession -ComputerName . # This dot indicates the local computer
You are connected to TOMMYSCOMPUTER.
6/11/2015 21:17:15 PM
[localhost]: PS C:\>

As I stared at this, I decided I would prefer that the date and time was centered under the first line, with asterisks on either side, so that both lines were the same length. This means I would need to dynamically determine how many asterisks I would need on each side of my date and time, so that the asterisks, and the date and time text, would fill up as much space as the line above. It was a new PowerShell challenge, and so I ran with it.

Before we go further, let me start by showing you an image of the end results, in case the explanation wasn’t clear. Then, we’ll walk though what I did to accomplish this task.

Add-a-Dynamic-Number-of-Asterisks-2015-01

I’ve broken down my start up script into five different parts. As you’ll see, I did things very procedurally. I wasn’t worried about using a minimal amount of commands and variables. For those with more experience, you’ll easily see how things could have been much tidier. Let’s start with the first two lines.

$Computer = "You are connected to $env:COMPUTERNAME."
$Date = Get-Date -Format G

These two lines set one variable each: $Computer and $Date. Each of these values will be used in the next example.

$TopLength = $Computer.Length
$BottomLength = $Date.Length

This section, above, determines and stores the character length of the $Computer and $Date variables. We are going to need to add asterisks for the difference between these two lengths.

$DiffInLength = $TopLength - $BottomLength
$Before = [math]::Round($DiffInLength/2)
$After = $DiffInLength - $Before

This third section puts the difference between $TopLength and $BottomLength into a variable called $DiffInLength. It then creates a $Before variable to store 1/2 of $DiffInLength — it will round up, if this amount isn’t a whole number. It then creates an $After variable, which will hold the leftovers from the previous calculation.

$Before = '*' * $Before
$After = '*' * $After

Next, as seen above, we’ll overwrite our $Before and $After variables with new values. Each of those values will be a set of asterisks. How do we know how many? We multiply the asterisk symbol (a string character) by the numeric values stored in the $Before and $After variables.

Write-Host -Object $Computer -ForegroundColor Green
Write-Host -Object $Before$Date$After -ForegroundColor Green

If you’re still following along, we then write out our two lines. The first line is made up of the $Computer variable. Following that line, we write our first set of asterisks, the date, and then the second set of asterisks.

This might have been a lot to take in, so you may consider copying and pasting each line into your PowerShell host and watch as it creates the two lines for you. Regardless of the length of your computer name, your two lines will be the same length, too.

Now, that last statement isn’t always true. What if we changed $Computer to just be the computer name, such as $Computer = $env:COMPUTERNAME? I know what happens; it’s throws errors and won’t let you connect to the endpoint. This problem begins with us subtracting a larger number from a smaller number. I’ve added some logic to skip a good portion of our script, if the $TopLength is smaller than $BottomLength. I’ve include the full script below.

$Computer = "You are connected to $env:COMPUTERNAME."
$Date = Get-Date -Format G

$TopLength = $Computer.Length
$BottomLength = $Date.Length

If ($TopLength -gt $BottomLength) {
    $DiffInLength = $TopLength - $BottomLength
    $Before = [math]::Round($DiffInLength/2)
    $After = $DiffInLength - $Before
    $Before = '*' * $Before
    $After = '*' * $After
}

Write-Host -Object $Computer -ForegroundColor Green
Write-Host -Object $Before$Date$After -ForegroundColor Green

In case someone wants to a see a tidier version of this same script, then here it is. We still use the $Before and $After variables, but no others, outside of our $Computer and $Date variables.

$Computer = "You are connected to $env:COMPUTERNAME."
$Date = Get-Date -Format G

If ($Computer.Length -gt $Date.Length) {
    $Before = '*' * ([math]::Round(($Computer.Length - $Date.Length)/2))
    $After = '*' * $Before.Length
}

Write-Host -Object $Computer -ForegroundColor Green
Write-Host -Object $Before$Date$After -ForegroundColor Green

 

Compare Roles and Feature between Servers

We have many different ways to build our servers so they are, and stay, the same: Desired State Configuration, is one way. In case you’re not there yet (and, I’m not, entirely), you may need to compare the roles and features between your servers to check for any differences.

In this first example, we’ll create two variables to store the installed roles and features of two different servers. Keep in mind that I’m working from a Windows 8.1 client, and two Server 2012 R2 servers. You’ll need to make sure you’re using these versions, as the Get-WindowsFeature cmdlet in 8.1 returns an error when trying to connect to a 2008 R2 server. As well, some of the Roles and Features’ names have changed. Therefore, I decided to only focus on 2012 R2.

PS> $DC01 = Get-WindowsFeature -Computer DC01 | Where-Object InstallState -eq Installed
PS> $NewDC = Get-WindowsFeature -Computer NewDC | Where-Object InstallState -eq Installed

Now that we have our populated variables, we can do our comparison between the two on their Name properties.

PS> Compare-Object -ReferenceObject $DC01.Name -DifferenceObject $NewDC.Name

InputObject                                                 SideIndicator
-----------                                                 -------------
AD-Domain-Services                                          <=
DNS                                                         <=
RSAT-DNS-Server                                             <=
RSAT-File-Services                                          <=
RSAT-DFS-Mgmt-Con                                           <=

If you want to view the roles and features that are installed on both servers, then include the -IncludeEqual parameter on Compare-Object, such as in the example below.

PS> Compare-Object -ReferenceObject $DC01.Name -DifferenceObject $NewDC.Name -IncludeEqual

InputObject                                                 SideIndicator
-----------                                                 -------------
NET-Framework-Features                                      ==
NET-Framework-Core                                          ==
NET-Framework-45-Features                                   ==
NET-Framework-45-Core                                       ==
NET-WCF-Services45                                          ==
NET-WCF-TCP-PortSharing45                                   ==
AD-Domain-Services                                          <=
DNS                                                         <=
RSAT-DNS-Server                                             <=
RSAT-File-Services                                          <=
RSAT-DFS-Mgmt-Con                                           <=

What might not be immediately evident for some, is that we could have skipped setting the first two variables and only used a single Compare-Object command. It can retrieve the information from each server and then compare it, at nearly the same time. We’ve remove the -IncludeEqual parameter is this example.

Compare-Object -ReferenceObject ((Get-WindowsFeature -ComputerName DC01 | Where-Object InstallState -eq Installed).Name) -DifferenceObject ((Get-WindowsFeature -ComputerName NewDC | Where-Object InstallState -eq Installed).Name)

InputObject                                                 SideIndicator
-----------                                                 -------------
AD-Domain-Services                                          <=
DNS                                                         <=
RSAT-DNS-Server                                             <=
RSAT-File-Services                                          <=
RSAT-DFS-Mgmt-Con                                           <=

Based on these results, and the ones further above, we have determined that our reference computer, DC01, has five additional packages installed.

Script Sharing – Quickly Remove and Import a Module (Reset-Module)

Often in Windows PowerShell module development, you’ll need to remove and import a module again, and again, to get the newest changes to your functions. It wasn’t long after some recent coding, that I tired from entering rmo mymodule and ipmo mymodule (the rmo alias is for Remove-Module and ipmo is used for Import-Module). Even when I added both commands to the same line, with a semi-colon in between: rmo mymodule; ipmo mymodule, and used wildcards: rmo mym*;ipmo mym*, I was still annoyed it was taking too much time.

Enter this quick function and alias:

Set-Alias -Name dump -Value Reset-Module
Function Reset-Module {
    [CmdletBinding()]
    Param ()

    Begin {
    } # End Begin.

    Process {
        Write-Verbose -Message 'Removing module.'
        Remove-Module -Name mymodule -ErrorAction SilentlyContinue -Verbose:$false
        Write-Verbose -Message 'Adding module.'
        Import-Module -Name mymodule -Verbose:$false
    } # End Process.

    End {
    } # End End.
} # End Function.

Now, I simply enter dump and it will remove and import the module again, with the newest changes. It’s PowerShell: You can use it to develop, and use it to speed up development.

Note: In case you’re thinking it, yes, there are many things that can be improved in this function: checking for the module before trying to remove it (and eliminating the -ErrorAction parameter in Remove-Module), not hard-coding the module’s name and using a parameter instead, and adding help. Even so, I wanted to throw something together quickly that probably wouldn’t live long after the end of the module’s development. Providing it does, I can add some refinements later.

Return File Sizes in Bytes, KBs, MBs, and GBs, at the Same Time

I scanned a recent forum post on PowerShell.org where the user seemed (again, I scanned it) to want to run some files through Get-ChildItem and Select-Object, and report the size in something other than the bytes default. It didn’t appear he, or she, wanted all the files in one of the measurement groups: B, KB, MB, or GB, but instead in their proper measurement group, dependent on the file’s size.

That may be hard to understand at first read, so let me try and explain it another way. If a file has less than 1,024 bytes, then it should be reported in bytes, if it has 1,024 – 1,048,575 bytes, then it should be reported in Kilobytes, if it has 1,048,576 – 1,073,741,824 bytes then it should be reported Megabytes, and if it has 1,073,741,825 or more bytes, then it should be reported in Gigabytes.

I wrote a long, “one-liner” to do this and have included it, and some sample output, below. I don’t profess to guarantee this doesn’t have any errors, so if you find some, then let me know. In addition, it shows a nice example of a switch statement inside the Expression portion of a calculated property. Fancy.

Get-ChildItem -Path 'C:\test' |
    Select-Object Name,
        @{L='Size';E={
                switch ($_.Length) {
                    # Bytes
                    {$_ -eq 0} {"$('{0:N2}' -f $_) bytes"; break}
                    {$_ -eq 1} {"$('{0:N2}' -f $_) byte"; break}
                    {($_ -gt 1) -and ($_ -le 1023)} {"$('{0:N2}' -f $_) bytes"; break}

                    # Kilobytes
                    {$_ -eq 1024} {"$('{0:N2}' -f ($_/1KB)) Kilobyte"; break}
                    {($_ -gt 1024) -and ($_ -le 1048575)} {"$('{0:N2}' -f ($_/1KB)) Kilobytes"; break}

                    # Megabytes
                    {$_ -eq 1048576} {"$('{0:N2}' -f ($_/1MB)) Megabyte"; break}
                    {($_ -gt 1048576) -and ($_ -le 1073741824)} {"$('{0:N2}' -f ($_/1MB)) Megabytes"; break}

                    # Gigabytes
                    {$_ -eq 1073741825} {"$('{0:N2}' -f ($_/1GB)) Gigabyte"; break}
                    {$_ -gt 1073741825} {"$('{0:N2}' -f ($_/1GB)) Gigabytes"; break}

                    default {Write-Warning -Message 'Unknown Error.'}
                }
            }
        } | Format-Table -AutoSize

Name           Size
----           ----
My-A-File.txt  1.00 byte
My-A-File2.txt 445.00 bytes
My-B-File.txt  1.00 Kilobyte
My-B-File2.txt 1.30 Kilobytes
My-C-File.txt  2.09 Megabytes
My-D-File.txt  3.59 Gigabytes

I can easily see how someone might want to remove the singular vs. plural: Kilobyte vs. Kilobytes, and just use B, KB, MB, and GB, and so…

Update: I gave this some more thought today, and I really didn’t feel like it was okay to not include a modified version that uses abbreviations (B, KB, MB, GB) instead of the full words, as in the previous example. It has removed some complexity, as well, as you can see below.

Get-ChildItem -Path 'C:\test' |
    Select-Object Name,
        @{L='Size';E={
                switch ($_.Length) {
                    # Bytes
                    {($_ -ge 0) -and ($_ -le 1023)} {"$('{0:N2}' -f $_) B"; break}

                    # Kilobytes
                    {($_ -ge 1024) -and ($_ -le 1048575)} {"$('{0:N2}' -f ($_/1KB)) KB"; break}

                    # Megabytes
                    {($_ -ge 1048576) -and ($_ -le 1073741824)} {"$('{0:N2}' -f ($_/1MB)) MB"; break}

                    # Gigabytes
                    {$_ -ge 1073741825} {"$('{0:N2}' -f ($_/1GB)) GB"; break}

                    default {Write-Warning -Message 'Unknown Error.'}
                }
            }
        } | Format-Table -AutoSize

Name           Size
----           ----
My-A-File.txt  1.00 B
My-A-File2.txt 445.00 B
My-B-File.txt  1.00 KB
My-B-File2.txt 1.30 KB
My-C-File.txt  2.09 MB
My-D-File.txt  3.59 GB