Viewing entries tagged
quick

PowerShell Quick Tip: Using ValidateSet

PowerShell Quick Tip: Using ValidateSet

PowerShell Quick Tip: Using ValidateSet

Why Use ValidateSet?

ValidateSet is part of advanced parameters. It allows you to constrain the input of the parameter to a set of values. It also will autocomplete those options for you as you tab through them!

This can really come in handy if you only want to accept a specific set of options as input to the parameter. I've found it useful in the following scenarios:

  • Scripts that work with AD, and you'd like a specific set of options for a given parameters
  • Scripts that work with an API, and only a specific set of values are accepted
  • Scripts that are used in web parsing where you'd like to constrain the options for a specific parameter
  • You want to reduce the amount of logic in your script, and offload it to parameter validation (added bonus: error generation for you)

Using ValidateSet

Using ValidateSet is easy! You just add the following line above your parameter:

[ValidateSet('Green','Blue','Red')]

The above example will ensure the input to the parameter we create is either Green, Blue, or Red.

Here is a simple function I put together to demonstrate how this works:

function Write-Color {
    [cmdletbinding()]
    param(       
        [Parameter(Mandatory)]  
        [ValidateSet('Green','Blue','Red')]
        [string]
        $color,
        $message
    )

    Write-Host $message -ForegroundColor $color 

}

When a function is in memory in the ISE, and ValidateSet is used, it will actually give you a visual list of the available options!

With this function in memory, let's run these commands and see what happens:

Write-Color -color Blue -Message "Validate: Blue"
Write-Color -color Red -Message "Validate: Red"
Write-Color -color Green -Message "Validate: Green"

That worked!

What if we used a color that's not in the group specified?

Write-Color -color DarkBlue -message "Validate: DarkBlue"

The command will also auto-complete the options for you in the console if you specify -color and then hit tab.

Limitations

There are some limitations when doing this. 

  • If you set a default value to one outside the array of options, it will work as it only checks incoming input
Hard to see, but it worked even though DarkBlue isn't in the set above!

Hard to see, but it worked even though DarkBlue isn't in the set above!

 

  • You're unable to generate your own error messages based on what happens in the function
    • This is fine, though, as you can wrap this up in a Try/Catch outside the function!
function Write-Color {
    [cmdletbinding()]
    param(       
        [Parameter()]  
        [ValidateSet('Green','Blue','Red')]
        [string]
        $color,
        $message
    )

    Write-Host $message -ForegroundColor $color 

}

Try {

    Write-Color -color Yellow -message "This will not work!" 

}

Catch [System.Management.Automation.ParameterBindingException] {

    $errorMessage = $_.Exception.Message
    Write-Host "Error: [$errorMessage]" -ForegroundColor Red -BackgroundColor DarkBlue
    <#

    Code to handle error

    #>
}

Error message after running:

Instead of using Write-Host, and merely showing the error, you'd want to have code in place that takes action based on the specific event.

Wrap Up

That's about it for using ValidateSet! It can really come in handy, and save you time when writing out your scripts.

Do you use ValidateSet? Leave a comment, and let me know how you use it. I always love hearing different use cases.

PowerShell: Calculating Folder Sizes

PowerShell: Calculating Folder Sizes

Sometimes all you want to know, or need to know, is how big a folder is in PowerShell. To do that, we'll need to use Get-ChildItem and Measure-Object specifically.

The quick and dirty

The first command we'll want to use is Get-ChildItem, and specify the base folder we'll want to look at. We'll also want to use the -Recurse switch with Get-ChildItem so we include the size of all the sub folders in the calculation as well.

We'll then need to pipe that to Measure-Object, which will allow us to look at the property Length, and calculate the Sum. This will be in bytes, which I will convert to megabytes via the string format operator (-f).  I will also use the format operator to only show 2 decimal places in the number returned. 

The one liner

"{0:N2} MB" -f ((Get-ChildItem C:\users\ -Recurse | Measure-Object -Property Length -Sum -ErrorAction Stop).Sum / 1MB)

This command returns the folder size of the C:\users folders in megabytes. 

The not-so-one-liner

I've created a module for this on GitHub, which will be updated more frequently than the code below. Check it out here

You can also install it from the PowerShell Gallery via:

Install-Module PSFolderSize

Then for help on how to run it, try:

Get-Help Get-FolderSize -Detailed

Moving on to how it all started!

This is my favorite. I'm not fond of one-liners as they are hard to reuse easily.

I created script, complete with comment-based help available with Get-Help

Here are some screenshots of the script in action:

.\Get-FolderSize.ps1
.\Get-FolderSize.ps1 -Path 'C:\Program Files'
$folderSizes = .\Get-FolderSize.ps1
$folderSizes | Where-Object {$_.'Size(MB)' -ne 'Empty'}
.\Get-FolderSize.ps1 -Path 'C:\Program Files' -Name IIS

Feel free to copy/paste, and use the code as you like! 
Let me know if you have any feedback below in the comments section.

<#
.SYNOPSIS

Get-FolderSize.ps1
Returns the size of folders in MB and GB.

.DESCRIPTION

This function will get the folder size in MB and GB of folders found in the basePath parameter. 
The basePath parameter defaults to C:\Users. You can also specify a specific folder name via the folderName parameter.

VERSION HISTORY:
1.0 - Updated object creation, removed try/catch that was causing issues 
0.5 - Just created!


.PARAMETER BasePath

This parameter allows you to specify the base path you'd like to get the child folders of.
It defaults to C:\.

.PARAMETER FolderName

This parameter allows you to specify the name of a specific folder you'd like to get the size of.

.PARAMETER AddTotal

This parameter adds a total count at the end of the array

.PARAMETER OmitFolders

This parameter allows you to omit folder(s) (array of string) from being included

.EXAMPLE 

.\Get-FolderSize.ps1
-------------------------------------

FolderName                Size(Bytes) Size(MB)     Size(GB)
----------                ----------- --------     --------
$GetCurrent                    193768 0.18 MB      0.00 GB
$RECYCLE.BIN                 20649823 19.69 MB     0.02 GB
$SysReset                    53267392 50.80 MB     0.05 GB
Config.Msi                            0.00 MB      0.00 GB
Documents and Settings                0.00 MB      0.00 GB
Games                     48522184491 46,274.36 MB 45.19 GB

.EXAMPLE 

.\Get-FolderSize.ps1 -BasePath 'C:\Program Files'
-------------------------------------

FolderName                                   Size(Bytes) Size(MB)    Size(GB)
----------                                   ----------- --------    --------
7-Zip                                            4588532 4.38 MB     0.00 GB
Adobe                                         3567833029 3,402.55 MB 3.32 GB
Application Verifier                              353569 0.34 MB     0.00 GB
Bonjour                                           615066 0.59 MB     0.00 GB
Common Files                                   489183608 466.52 MB   0.46 GB

.EXAMPLE 

.\Get-FolderSize.ps1 -BasePath 'C:\Program Files' -FolderName IIS
-------------------------------------

FolderName Size(Bytes) Size(MB) Size(GB)
---------- ----------- -------- --------
IIS            5480411 5.23 MB  0.01 GB

.EXAMPLE

$getFolderSize = .\Get-FolderSize.ps1 
$getFolderSize 
-------------------------------------

FolderName Size(GB) Size(MB)
---------- -------- --------
Public     0.00 GB  0.00 MB
thegn      2.39 GB  2,442.99 MB

.EXAMPLE

Sort by size descending 
$getFolderSize = .\Get-FolderSize.ps1 | Sort-Object 'Size(Bytes)' -Descending
$getFolderSize 
-------------------------------------

FolderName                Size(Bytes) Size(MB)     Size(GB)
----------                ----------- --------     --------
Users                     76280394429 72,746.65 MB 71.04 GB
Games                     48522184491 46,274.36 MB 45.19 GB
Program Files (x86)       27752593691 26,466.94 MB 25.85 GB
Windows                   25351747445 24,177.31 MB 23.61 GB

.EXAMPLE

Omit folder(s) from being included 
.\Get-FolderSize.ps1 -OmitFolders 'C:\Temp','C:\Windows'

#>
[cmdletbinding()]
param(
    [Parameter(Mandatory = $false)]
    [Alias('Path')]
    [String[]]
    $BasePath = 'C:\',        
    [Parameter(Mandatory = $false)]
    [Alias('User')]
    [String[]]
    $FolderName = 'all',
    [Parameter()]
    [String[]]
    $OmitFolders,
    [Parameter()]
    [Switch]
    $AddTotal
)

#Get a list of all the directories in the base path we're looking for.
if ($folderName -eq 'all') {

    $allFolders = Get-ChildItem $BasePath -Directory -Force | Where-Object {$_.FullName -notin $OmitFolders}

}
else {

    $allFolders = Get-ChildItem $basePath -Directory -Force | Where-Object {($_.BaseName -like $FolderName) -and ($_.FullName -notin $OmitFolders)}

}

#Create array to store folder objects found with size info.
[System.Collections.ArrayList]$folderList = @()

#Go through each folder in the base path.
ForEach ($folder in $allFolders) {

    #Clear out the variables used in the loop.
    $fullPath = $null        
    $folderObject = $null
    $folderSize = $null
    $folderSizeInMB = $null
    $folderSizeInGB = $null
    $folderBaseName = $null

    #Store the full path to the folder and its name in separate variables
    $fullPath = $folder.FullName
    $folderBaseName = $folder.BaseName     

    Write-Verbose "Working with [$fullPath]..."            

    #Get folder info / sizes
    $folderSize = Get-Childitem -Path $fullPath -Recurse -Force -ErrorAction SilentlyContinue | Measure-Object -Property Length -Sum -ErrorAction SilentlyContinue       
        
    #We use the string format operator here to show only 2 decimals, and do some PS Math.
    $folderSizeInMB = "{0:N2} MB" -f ($folderSize.Sum / 1MB)
    $folderSizeInGB = "{0:N2} GB" -f ($folderSize.Sum / 1GB)

    #Here we create a custom object that we'll add to the array
    $folderObject = [PSCustomObject]@{

        FolderName    = $folderBaseName
        'Size(Bytes)' = $folderSize.Sum
        'Size(MB)'    = $folderSizeInMB
        'Size(GB)'    = $folderSizeInGB

    }                        

    #Add the object to the array
    $folderList.Add($folderObject) | Out-Null

}

if ($AddTotal) {

    $grandTotal = $null

    if ($folderList.Count -gt 1) {
    
        $folderList | ForEach-Object {

            $grandTotal += $_.'Size(Bytes)'    

        }

        $totalFolderSizeInMB = "{0:N2} MB" -f ($grandTotal / 1MB)
        $totalFolderSizeInGB = "{0:N2} GB" -f ($grandTotal / 1GB)

        $folderObject = [PSCustomObject]@{

            FolderName    = 'GrandTotal'
            'Size(Bytes)' = $grandTotal
            'Size(MB)'    = $totalFolderSizeInMB
            'Size(GB)'    = $totalFolderSizeInGB
        }

        #Add the object to the array
        $folderList.Add($folderObject) | Out-Null
    }   

}
    
#Return the object array with the objects selected in the order specified.
Return $folderList

If you're just getting started in PowerShell, and would like some help, check out my series on Getting Started With Windows PowerShell.

As always, feedback is appreciated, and let me know if you have any questions.

-Ginger Ninja

 

 

PowerShell Quick Tip: Basic use of the -f format operator

PowerShell Quick Tip: Basic use of the -f format operator

What the -f?

There are many ways to manipulate/build strings in PowerShell. One of my favorite ways is to use the -f format operator. When you use the operator, it evaluates everything to the right of -f as an array that starts at index value 0 (as all PowerShell arrays do). The values are comma separated.

Setup

For this demonstration I setup the following variables:

[array]$formatArray = @('you','values','them.')
$user               = (Get-ChildItem Env:\USERNAME).Value
$date               = Get-Date

Using the -f operator

Example one

"Your user name is {0}, and the time is [{1:HH}:{1:mm}:{1:ss}]" -f $user,$date

Returns

Your user name is Mike, and the time is [12:54:44]

Example two

Since it is indexing values as an array (starting at zero), you can actually feed it an array. Let's look at the values of the $formatArray array.

0 = you
1  = values
2 = them

Knowing that now, let's run this command:

"These {1} go where {0} place {2}" -f $formatArray

Returns

These values go where you place them.

Pretty cool, huh? There are multiple uses for this! Here is one example from a logging module I wrote. The context here is the user used the -addDate $true argument, and I need to append the date to the log name.

    if ($addDate) {
        
        if ($logName.Contains('.')) {
            
            $logName = $logName.SubString(0,$logName.LastIndexOf('.')) + "_{0:MM-dd-yy_HHmm}" -f (Get-Date) + $logName.Substring($logName.LastIndexOf('.'))
            
            Write-Verbose "Adding date to log file with an extension! New file name: $logName"
            Write-Verbose ""
           
        } else {
            
            $logName = $logName + "_{0:MM-dd-yy_HHmm}" -f (Get-Date)
            
            Write-Verbose "Adding date to log file. New file name: $logName"
            Write-Verbose ""
            
        }
         
    }

But wait... there's more!

As you can see in the examples above you can use the value with a colon and then specify more options. For these examples I used the options for getting specific values returned by Get-Date such as:

{1:HH}:{1:mm}:{1:ss}

This allows us to enumerate the values specifically for the 24 hour hour, minutes, and seconds returned by $date (which is where Get-Date is stored from the setup above).

There are other options you can use to to further format your strings. There are ones for different placements, spaces, and even returning different number values.  To check out the full details of what the -f format operator can do, check out these links!

As always, let me know if you have any questions or ideas in the comments section below!

-Ginger Ninja

PowerShell: List out your PuTTY sessions

PowerShell: List out your PuTTY sessions

I wanted a way to export my PuTTY sessions as I was about to reformat my machine. You could export the registry keys and keep them, but I wanted something more visual as well. So I turned to my dear old trusty friend, PowerShell.

What you'll need

  • PowerShell
  • PuTTY installed with at least one session
  • The ability to overlook Write-Host as an always bad thing
    • It's only mostly always bad, ok? But I wanted some formatting and colors
      • I'm only judging myself a little bit.

Let's jump into it! (The code)

$sessions          = Get-ChildItem 'HKCU:\Software\SimonTatham\PuTTY\Sessions'
[array]$properties = @('HostName','TerminalType','TerminalSpeed')

Write-Host 'Listing PuTTy sessions' -ForegroundColor Green `n
foreach ($session in $sessions) {
    
    $name       = ($session.Name.Substring($session.Name.LastIndexOf("\") + 1)).Replace("%20"," ")
    Write-Host $name -ForegroundColor White
    
    Foreach ($property in $properties) {
                
        Write-Host -NoNewLine `t $property":" -ForegroundColor Green
        Write-Host $session.GetValue($property)
        
        }

    Write-Host `n
    
}

There isn't too much to this one, but it's also the tip of the iceberg. I chose to make it more human readable as if you simply did this:

Get-ChildItem HKCU:\Software\SimonTatham\PuTTY\Sessions

The output is all of the key's properties:

So what I did is cherry picked some of the values I wanted to display.

[array]$properties = @('HostName','TerminalType','TerminalSpeed')

I then used a foreach loop to go through each property listed and display it for that session.

Foreach ($property in $properties) {

Write-Host -NoNewLine `t $property":" -ForegroundColor Green
Write-Host $session.GetValue($property)

}

The execution

My PuTTY session is pretty lonely, but just one will work!

PowerShell code execution:

I named my script Backup-Putty.ps1 as I initially made it as a way to get a quick human readable copy and paste of the sessions I cared about. With more effort you could easily make this script output the list into a CSV and store it that way.

PowerShell Quick Tip: Use a text file to omit values in an array

PowerShell Quick Tip: Use a text file to omit values in an array

After making the Lun Check Script I had an issue come up when somebody decommissioned a server. The LUNs that were mapped needed to be set offline for a week, then deleted. Since the script would see if ANY LUNs (sans ones with test in the name) were offline, this triggered an alert. 

To mitigate this problem, I added a text file that the script imports and uses to exclude any LUNs with the names included in the file in their path.

To demonstrate this, here is an example text file contents:

Exclude.txt File contents:
test
al*
delta
th*

$processThis would be the full list of LUNs, for example.

$exclude = Get-Content C:\exclude.txt
$processThis = @('test','delta','alpha','this','that','and','the','other','thing')

We can try to use -notin to see how that works when excluding the contents of Exclude.txt...

$processThis | Where-Object {$_ -notin $exclude}
alpha
this
that
and
the
other
thing

Well, that worked for 'test' and 'delta', but not the wildcard entries! I need to accommodate wildcards per my team's request.  To do this I used a ForEach-Object with Where-Object nested inside of it.

$omit = $exclude | ForEach-Object{$no = $_;$processthis | Where-Object{$_ -like $no}}

Now $omit will contain the full list of items to omit.

$omit
test
alpha
delta
this
that
the
thing

And we can finally use Where-Object with -notin to finish it up.

$processThis | Where-Object {$_ -notin $omit}
and
other

I hope you found this helpful! If you know of a better way, or have any questions, let me know!

-Ginger Ninja

PowerShell Quick Tip: Simple logging with time stamps

PowerShell Quick Tip: Simple logging with time stamps

Today I was writing a script at work to check sysvol replication. We have a DC that will sometimes not share the love outbound, yet inbound works just fine. That means to test it I needed to create a file on that DC, and ensure it replicates outward. Normally, I would check event log or use WMI queries, however everything looks good even when it isn't working.

After writing the script, I wanted to add some very simple logging so I could save the results a file (which is then emailed out). 

KISS 

I kept this simple, as I needed something that I could write while not taking up too much time to do it. In the script, I created a function to format a time stamp.

Timestamp function

function Get-TimeStamp {
    
    return "[{0:MM/dd/yy} {0:HH:mm:ss}]" -f (Get-Date)
    
}

Told you it was simple! You can use it with Write-Output and use Out-File to add content into a log file.

Write-Output "$(Get-TimeStamp) I can connect to $dc" | Out-file C:\dclog.txt -append

Here are the results in the text file from the script after it runs:

[2/15/2016 9:27 AM] File test927AM created dc1
[2/15/2016 9:27 AM] File test927AM exists on dc1
[2/15/2016 9:30 AM] File test927AM removed from dc1
[2/15/2016 9:32 AM] I can connect to dc1

I added some Start-Sleep statements in there as you can see with the time stamp differences. Once I tidy up the sysvol replication checker script I'll share that here.

I hope you found this helpful, let me know if you have a better way or any questions/comments!

-Ginger Ninja

PowerShell Quick Tip: Basic use of the Switch statement

PowerShell Quick Tip: Basic use of the Switch statement

Why use Switch?

In PowerShell you can use an if statement to compare things and determine actions based upon the result. You also can use elseif to keep comparing values until you reach a desired (or not so desired) result.

Switch allows you to take a variable you define, and then based upon its value perform different actions.

Example

In this example I will show you a switch statement I used in a script that uses the Weather Underground API to send HTML emails detailing your requested weather forecast. Consider this a teaser until I post that script itself.

What I wanted to do

In this example I wanted to take a returned percentage, and then change a variable to display the appropriate text result. It compares the returned value of the hour's chance of precipitation set in $hourPrecip, and then based on that number changes the variable $popText accordingly.

The Code

Switch ($hourPrecip) {
                        
                        {$_ -lt 20} {
                            
                            $popText = 'No mention'
                            
                        }
                        
                        {$_ -eq 20} {
                            
                            $popText = 'Slight Chance'
                            
                        }
                        
                        {($_ -lt 50 -and $_ -gt 20)} {
                            
                            $popText = 'Chance'
                            
                        }
                        
                        {$_ -eq 50} {
                            
                            $popText = 'Good chance'
                            
                        }
                        
                        {($_ -lt 70 -and $_ -gt 50)} {
                            
                            $popText = 'Likely'
                            
                        }
                        
                        {$_ -ge 70} {
                            
                            $popText = 'Extremely likely'
                            
                        }
                        
                    }

So now you display the percentage, as well as its correlating text value which describes what the percentage means.

If the value was 51 for $hourPrecip, then $popText will be set to Likely

The Weather Underground post will be coming soon!

Do you use switches in your scripts, and if so, how do you like to use them?