Saturday, August 18, 2012

PowerShell V3 - $PSDefaultParameterValues

One of the new features of PowerShell V3 that I find very helpful is having the ability to automatically pass default parameters to cmdlets or advanced functions.

Here are a few snippets of code that shows how to manipulate them.  Simply include your default parameters in your profile and script on!

# Intitial creation (perhaps place in profile)            
$PSDefaultParameterValues=@{            
"Send-MailMessage:SMTPServer"="smtp.osumc.edu";            
"Send-MailMessage:From"="Automated.Email@osumc.edu";            
"Get-Eventlog:logname"="Security";            
}            
            
# Add another one            
$PSDefaultParameterValues.Add("Send-MailMessage:Bcc","PhatBeard@osumc.edu")                  
# opps, need to modify the email address            
$PSDefaultParameterValues["Send-MailMessage:Bcc"]="FatBeard@osumc.edu"                       
# Add another one (straight out of Get-Help) that utilizes a scriptblock
$PSDefaultParameterValues+=@{"Format-Table:AutoSize"={if ($host.Name –eq "ConsoleHost"){$true}}}            
            
# Remove one            
$PSDefaultParameterValues.Remove("Get-Eventlog:logname")            
            
# Remove all of them            
$PSDefaultParameterValues.Clear()            
            
# Temporarily disable all parameters            
$PSDefaultParameterValues.Add("Disabled", $true)            
            
# Remove the temporary disable            
$PSDefaultParameterValues.Remove("Disabled")
Enjoy!

Tuesday, August 14, 2012

Windows PowerShell for Developers by Douglas Finke; O’Reilly Media


It is not often that a technical book makes you rethink how you think. Doug Finke (a Microsoft Most Valuable Professional) has achieved just that with this concise PowerShell reference.

He starts out our journey describing PowerShell as a glue language that is as programmable as Perl, Python and Ruby and takes its cues from Unix Shells. The next few steps describe getting started, and include a brief tour. He then shifts into high gear as we learn about template engines, adding PowerShell to our GUI apps and creating graphical applications within PowerShell.

The chapter on “Writing Little Languages in PowerShell” was a welcome paradigm shift for me. Having virtually no experience with Domain Specific Languages (DSL), it was a fun ride as Doug demonstrated how to create a better XML and a creating a DSL using Graphviz. The lessons in this chapter alone were worth the price of the book.

He completes our tour with coverage of integration with COM (Component Object Model – specifically Microsoft Excel) and some of highlights of PowerShell V3 (Workflows, JSON).

This was an enjoyable invigorating read; in fact, I went through it multiple times. I appreciate the developer-centric perspective that Doug displayed throughout the text.  Whether you are a seasoned developer or a weekend hacker, if you have any interest in PowerShell, I encourage you to pick up “Windows PowerShell for Developers”.

Tuesday, August 7, 2012

PowerShell, Diskpart and Exchange (Oh my!)

Was given the opportunity to work a bit on an Exchange 2007 - 2010 migration.  I was asked if supplied a csv that contained Server and folders could the following be scripted:
  • Step 1 - Create a mount point to hold the drives (M:)
  • Step 2 - Create a series of folders on the above drive
  • Step 3 - Create a series of volumes to be used to hold the database and log files
After a bit of research, it seemed feasible using a combination of PowerShell and DiskPart.
It took me a few tries to get the hang of the necessary DiskPart commands to string together to pipe, my experience with disks has primarily been with the GUI, so I able to add a bit more to my command line toolbelt.

So once the DiskPart commands were figured out, all that needed to be done is run these on the (new)Exchange servers.  Enter Invoke-Command - you should be hearing cheering, whistling and much applause as this is darn near the most useful cmdlet in PowerShell V2.

Following is a script that could be used to create 100s of volumes.

############## Step 1 ##############            
# Create the mount point for the drives            
# We create a script block consisting of the commands we need to pipe to Diskpart            
$cmds = "`"Select Disk 2`"",            
        "`"create partition primary`"",            
        "`"assign letter=m`"",            
        "`"format fs = ntfs unit=64k quick label='Databases'`""            
$string = [string]::Join(",",$cmds)            
$sb = $ExecutionContext.InvokeCommand.NewScriptBlock("$string | DiskPart")            
            
# Iterate over the 6 Exchange Servers using Invoke-Command to run or            
# script block on each server            
1..6 | foreach {            
    Invoke-Command -ComputerName "ex10mbox-vp0$_" -ScriptBlock $sb            
}            
            
############## Step 2 ##############            
# Using a supplied CSV file, create our directories            
$folders = Import-Csv -Path C:\temp\BrentFolders.csv             
$folders | foreach {            
    $path = "\\$($_.server)\M$\$($_.folder)"            
    if(-not(Test-Path -Path $path)) {            
        New-Item -Path $path -ItemType Directory            
    }            
}            
            
############## Step 3 ##############            
# Create the 10 volumes that will be used to hold the individual database and log files.            
$disk = 3            
foreach ($folder in $folders) {            
    if($i -eq 13){$i=3}            
    $cmds = "`"select disk $disk`"",            
            "`"online disk`"",            
            "`"attributes disk clear readonly`"",            
            "`"convert mbr`"",            
            "`"create partition primary`"",            
            "`"assign mount=M:\$($folder.Folder)`"",            
            "`"format fs = ntfs unit=64k quick label=$($Folder.Folder)`""             
    $disk++            
    $string = [string]::Join(",",$cmds)            
    $sb = $ExecutionContext.InvokeCommand.NewScriptBlock("$string | DiskPart")            
    Invoke-Command -ComputerName $folder.Server -ScriptBlock $sb            
}            
This could also be utilzed for SQL Server rollouts, etc.

Enjoy!

Wednesday, August 1, 2012

"Introducing Regular Expressions" By Michael Fitzgerald; O'Reilly Media


Michael Fitzgerald achieves his goal of introducing the reader to Regular Expressions.  He clearly states his intent and his expected audience in the introduction.  In addition to the inductive approach to teaching the basics of Regular Expressions to the reader, he takes the opportunity to introduce a plethora of (free) tools.  They include but are not limited to the following:

The chapters build upon each other starting with the basics:
  • Simple Pattern Matching
  • Boundaries
  • Alternation, Groups and Backreferences
  • Character Classes
  • Matching Unicode and Other Characters
  • Quantifiers
  • Lookarounds
The lessons learned in the chapters listed above are put to use in Chapter 9, "Marking up a Document with HTML".

In addition to learning the basics of Regular Expressions, the reader gets (re)introduced to Samuel Taylor Coleridge's "The Rime of the Ancyent Marinere".  A nice change from the standard, technical examples.

This concise, book is worth the investment if you are new to Regular Expressions.  



Monday, June 25, 2012

PowerShell and Bass Guitar

I recently decided to try my hand at a musical instrument.  The bass guitar seemed like a good fit for me so I picked it up started a with a few lessons.  I quickly realized that in order to have any success with it, I needed to memorize the fret-board.  Given my affinity for PowerShell, I decided to blend the two.  Following is a script that I threw in my profile to prompt me upon start-up for a few notes.

function Get-BassNote {            
 param($Count=2)            
             
 $BassNotes = @{             
      "Fret0" = @{             
        "String1" = {E}            
        "String2" = {A}            
        "String3" = {D}            
        "String4" = {G} }            
      "Fret1" = @{            
        "String1" = {F}            
        "String2" = {A#}            
        "String3" = {D#}            
        "String4" = {G#} }            
      "Fret2" = @{            
        "String1" = {F#}            
        "String2" = {B}            
        "String3" = {E}            
        "String4" = {A} }            
      "Fret3" = @{            
        "String1" = {G}            
        "String2" = {C}            
        "String3" = {F}            
        "String4" = {A#} }            
      "Fret4" = @{            
        "String1" = {G#}            
        "String2" = {C#}            
        "String3" = {F#}            
        "String4" = {B} }            
      "Fret5" = @{            
        "String1" = {A}            
        "String2" = {D}            
        "String3" = {G}            
        "String4" = {C} }            
      "Fret6" = @{            
        "String1" = {A#}            
        "String2" = {D#}            
        "String3" = {G#}            
        "String4" = {C#} }            
      "Fret7" = @{            
        "String1" = {B}            
        "String2" = {E}            
        "String3" = {A}            
        "String4" = {D} }            
      "Fret8" = @{            
        "String1" = {C}            
        "String2" = {F}            
        "String3" = {A#}            
        "String4" = {D#} }             
      "Fret9" = @{            
        "String1" = {C#}            
        "String2" = {F#}            
        "String3" = {B}            
        "String4" = {E} }             
      "Fret10" = @{            
        "String1" = {D}            
        "String2" = {G}            
        "String3" = {C}            
        "String4" = {F} }            
      "Fret11" = @{            
        "String1" = {D#}            
        "String2" = {G#}            
        "String3" = {C#}            
        "String4" = {F#} }            
      "Fret12" = @{            
        "String1" = {E}            
        "String2" = {A}            
        "String3" = {D}            
        "String4" = {G} }            
    }            
             
    for ($i=1; $i-le$Count; $i++){            
        $fretNumber   = Get-Random -Minimum 0 -Maximum 12            
     $stringNumber = Get-Random -Minimum 1 -Maximum 4            
     [string]$Answer = $BassNotes."Fret$($FretNumber)"."String$($StringNumber)"            
     $prompt = "What is the note for string $($stringNumber), fret $($fretNumber)"            
     do { $Response = Read-Host $Prompt }             
        until ($Answer -eq $Response)            
 }            
}


Rock on!

Monday, April 16, 2012

Speed Reading with PowerShell

Many of you have had to read in large text files for processing in PowerShell.  The Get-Content cmdlet is perfect for this.  However, it can be very sloooow with large files.  There are multiple ways to speed this up.  For example, we could dive into .NET using the [System.IO.File]::ReadAllLines() method. For simplicity, let's stick with the Get-Content cmdlet.  Following is an example that demonstrates a couple different techniques, the one to focus on is the use of the "-ReadCount" parameter.

# define some random nouns, verbs and adverbs            
$noun = "Ed","Hal","Jeff","Doug","Don","Kirk","Dmitry"            
$verb = "ran","walked","drank","ate","threw","scripted","snored"       
$adverb = "quickly","randomly","erratically","slowly","slovenly","loudly"            
            
# create an array with 10,000 random sentences             
$content = 1..10000 | foreach {            
    "{0} {1} {2}." -f ($noun|Get-Random),($verb|Get-Random),($adverb|Get-Random)            
}            
            
# save our array to a text file            
$path = "c:\temp\RandomSentences.txt"              
$content | Out-File -FilePath $path            
            
# read in the files and measure the time taken.            
(measure-command -Expression { Get-Content -Path $path }).TotalMilliseconds
(measure-command { Get-Content $path -ReadCount 10 }).TotalMilliseconds
(measure-command { Get-Content $path -ReadCount 100}).TotalMilliseconds
(measure-command { Get-Content $path -ReadCount 1000}).TotalMilliseconds


The results....
164.6186
24.5987
19.7441
16.2411



Explanation

The Get-Content cmdlet does more behind the scenes then just present the data.  There are a properties being populated as it reads in the file.  By default, this happens for each line as it it read.  For large files, this overhead can be reduced by setting the -ReadCount parameter.  With this parameter set, you will only be manipulating the behind the scenes properties in a collection size that is equal to the number you set the -ReadCount attribute to.
  
Hope this helps!

Friday, March 2, 2012

Limit your use of the pipe

There have been many posts about the proper utilization of the powerful pipe. Filtering left to avoid piping to the Where-Object is always a good idea. Following is another example that demonstrates that judicious use of the pipe is a best practice.
We are going to define 3 scriptblocks that simply count to 100,000 and measure the time it takes them to run.
$limit = 100000         
$test1 = { foreach ($num in 1..$limit ) {$num} }
$test2 = { for($x=1; $x -le $limit; $x++) {$x} }
$test3 = { 1..$limit | foreach{$_} }

"ForEach: {0} seconds" -f (Measure-Command $test1).TotalSeconds
"For: {0} seconds" -f (Measure-Command $test2).TotalSeconds
"Pipe: {0} seconds" -f (Measure-Command $test3).TotalSeconds
On my machine these were the results:
ForEach: 0.0348825 seconds
For: 0.2490948 seconds
Pipe: 6.7627934 seconds

Enjoy!

Monday, February 20, 2012

PowerShell and MongoDB

I was exploring MongoDB last weekend and was a bit skeptical at first. The relational model (I used to teach it) is so ingrained into my way of thinking. Like the florescent bulbs in my garage during the Winter, the light slowly started to brighten. I can now see a lot of uses for a schema free database (especially as a tool for prototyping). After playing around with the JavaScript interface, I decided to see what PowerShell could do with it.

Luckily, there is a driver available for download.

Assuming you have MongoDB, the C# driver and PowerShell installed, you can play around with the following code:
# Add a reference to our dll        
Add-Type -Path 'C:\Program Files (x86)\MongoDB\CSharpDriver 1.3.1\MongoDB.Driver.dll'

# Name our test db (not actually created until we insert)
$db = [MongoDB.Driver.MongoDatabase]::Create('mongodb://localhost/PowerShellMongoTest');

# Name or test collection
$coll = $database["Stuff"]

# Define a couple list
$languages = @("C#","Haskell","PowerShell","Python")
$beers = @("Honkers Ale","Stella","Summer Shandy","Yuengling")

# Define our document
$doc = @{FirstName="Wes"; LastName="Stahler"; Languages=$languages; Beers=$beers}
$collection.Insert($doc)
$info = $collection.FindAll()
$info | Format-Table -AutoSize

#$collection.RemoveAll()

This yields:
Name       Value
---- -----
_id 4f42b6798359da1e7ce51bfb
Beers {Honkers Ale, Stella, Summer Shandy, Yuengling}
LastName Stahler
Languages {C#, Haskell, PowerShell, Python}
FirstName Wes

I will be experimenting more with MongoDB.
Share your insights with me if you decide to explore it as well.

Enjoy!

Tuesday, February 7, 2012

Calling vbScript via PowerShell

Following is an example of how to call a vbScript from PowerShell. I recently had to do something similar to this for a Postini SafeSender list conversion to Exchange 2007/AD.

First the vbScript:
Saved as CallFromPowerShell.vbs

Option Explicit
Dim strComputer, objWMI, OS

strComputer = WSH.Arguments(0)

On Error Resume Next
Set objWMI=GetObject("winmgmts://" & strComputer).InstancesOf("win32_operatingsystem")
If objWMI is nothing Then
WScript.Echo "Unable to connect to " & strComputer
Else
For Each OS In objWMI
wscript.Echo OS.Caption
Next
end If

To call this from PowerShell:

$computers = 'fatbeard-vp01','fatbeard-vp02','fatbeard-vp03'          
$computers |
foreach {"{0,20}`t{1}" -f $_,$(cscript.exe //nologo c:\temp\callfrompowershell.vbs $_) }

Yields...
fatbeard-vp01 Microsoft Windows 7 Enterprise
fatbeard-vp02 Microsoft(R) Windows(R) Server 2003, Enterprise Edition
fatbeard-vp03 Unable to connect to fatbeard-vp03

Monday, February 6, 2012

Project Euler #89

This one was fairly easy to do with PowerShell.
Problem 89 - " Develop a method to express Roman numerals in minimal form. "
$pre = $post = 0      
foreach ($line in Get-Content C:\temp\roman.txt) {
$pre += $line.Length
$line = $line -replace("DCCCC","CM")
$line = $line -replace("LXXXX","XC")
$line = $line -replace("VIIII","IX")
$line = $line -replace("IIII","IV")
$line = $line -replace("XXXX","XL")
$line = $line -replace("CCCC","CD")
$post += $line.Length
}
$pre-$post
Enjoy!

Tuesday, January 24, 2012

PowerShell - Let SQL sort it out

I came across a piece of code yesterday that provided a learning opportunity. The code was a simple SQL query that returned a list of computers from a database. The code I saw, had PowerShell handling the sort after the computers were retrieved from the SQL database. While this works, it is not a best practise. In fact, it something that Don Jones has often mentioned - Filter Left, Format Right. Look below to see the performance difference of letting SQL Server handle the sort.
"Filter Left: {0} seconds" -f (Measure-Command -Expression {           
$qry = "select name from vcomputer where [IsManaged] ='1' order by name"
$Altiris = Invoke-Sqlcmd -ServerInstance SQL01 -Database Altiris -Query $qry
}).TotalSeconds

"Filter Right: {0} seconds" -f (Measure-Command -Expression {
$qry = "select name from vcomputer where [IsManaged] ='1'"
$Altiris = Invoke-Sqlcmd -ServerInstance SQL01 -Database Altiris -Query $qry | sort
}).TotalSeconds


As you can see, this is a significant difference!
Like Active Directory, let the server that is good at filtering or sorting handle the work for you.

Enjoy!

Monday, January 23, 2012

Creating an LDIF file with PowerShell

Occasionally, I am asked to create a large batch of users for our eDirectory environment. Following is an example on how to create 500 test users (gotta love Here-Strings).
$path = "c:\temp\LDIF$(get-date -Format yyyyMMdd).txt"          
New-Item -Path $path -ItemType File -Force
Add-Content -Value "version: 1" -Path $path

100..600 | Foreach {
$value = @"

dn: cn=PSFTTest$_,ou=users,o=OSUMC
changetype: add
userPassword: P@ssw0rd
uid: PSFTTest$_
givenName: First$_
fullName: First$_ Last$_
sn: Last$_
objectClass: inetOrgPerson
objectClass: organizationalPerson
objectClass: Person
objectClass: Top
cn: PSFTTest$_
"@

Add-Content -Value $value -Path $path
}

Enjoy!

PowerShell - Project Euler Problem 102

I am always pleased when I can use PowerShell to solve a Project Euler problem. This one was straightforward. You are supplied with a text file containing 1000 random triangular coordinates and you need to determine how many of the triangles contain the origin (0,0). There are multiple ways to attack this, I went for he easier approach: if the area of the supplied triangle is equal to the 3 triangles created using the origin, then we know that the triangle contains the origin. I used Heron's Formula to calculate the area.

Following is the code used to find the answer.

<# Get side lengths 
$LengthAB = Length of AB = SquareRoot of (Ax - Bx)^2 + (Ay - By)^2
$LengthAC = Length of AC = SquareRoot of (Ax - Cx)^2 + (Ay - Cy)^2
$LengthBC = Length of BC = SquareRoot of (Bx - Cx)^2 + (By - Cy)^2

$s = .5($LengthAB +$LengthAC +$LengthAC)
$Area = SQRT($s($s-$LengthAB)($s-$LengthAC)($s-$LengthBC) #>
function Get-LengthOfSide {     
param([Array]$X,[Array]$Y)

return [Math]::sqrt( [Math]::pow(($X[0]-$Y[0]),2) + [Math]::pow(($X[1]-$Y[1]),2))
}

function Get-AreaOfTriangle {
param([Array]$X,[Array]$Y, [Array]$Z)

$LengthAB = Get-LengthOfSide -X $X -Y $Y
$LengthAC = Get-LengthOfSide -X $X -Y $Z
$LengthBC = Get-LengthOfSide -X $Y -Y $Z
$s = .5*($LengthAB+$LengthAC+$LengthBC)
$Area = [Math]::sqrt( $s*($s-$LengthAB)*($s-$LengthAC)*($s-$LengthBC) )
return $Area
}

$path = 'C:\Users\stah06\Documents\triangles.txt'
$uri = 'http://projecteuler.net/project/triangles.txt'

# Using Invoke-WebRequest (PowerShell V3)
#Invoke-WebRequest -Uri $uri -OutFile $path

# Using System.Net.WebClient (PowerShell V2)
$web = New-Object System.Net.WebClient
$web.DownloadFile($uri, $path)

$match = 0
Get-Content $path |
foreach {
$A = $_.split(",")[0],$_.split(",")[1]
$B = $_.split(",")[2],$_.split(",")[3]
$C = $_.split(",")[4],$_.split(",")[5]
$D = 0,0

$TriangleABC = Get-AreaOfTriangle -X $A -Y $B -Z $C
$TriangleABD = Get-AreaOfTriangle -X $A -Y $B -Z $D
$TriangleACD = Get-AreaOfTriangle -X $A -Y $C -Z $D
$TriangleBCD = Get-AreaOfTriangle -X $B -Y $C -Z $D

$SumofTriangles = $TriangleABD +$TriangleACD + $TriangleBCD

if ( [math]::abs($TriangleABC -$SumofTriangles) -lt .5) {
#"{0} {1}" -f $TriangleABC, $SumofTriangles
$match++
}
}

$match


Enjoy!

Thursday, January 19, 2012

Will Rogers Phenomenon

Following is a example of the Will Rogers phenomenon. This discussion was a tangent from a water-cooler discussion of the Monty Hall problem (much more interesting).

‹#
The Will Rogers phenomenon occurs when the averages
of 2 groups are raised by moving one item from one
to the other.Note: Data may not be truly
representative of actual figures. #›


# Sample IQs
$Ohio = 110,105,115,120
$StateUpNorth = 90,95,85,90

# Initial State (pun intended...)
"Average Ohio IQ before move is: {0}" -f ($Ohio |
Measure-Object -Average).Average

"Average StateUpNorth IQ before move is: {0}`n" -f ($StateUpNorth |
Measure-Object -Average).Average

# Rumoured to be in the Toledo area...
$LowestOhioIQ = ($Ohio sort)[0]

# Remove from Ohio
$Ohio = @($ohio where {$_ -ne $LowestOhioIQ})

# Add to State up North (Ann Arbor area)
$StateUpNorth+= $LowestOhioIQ

# Final State
"Average Ohio IQ after move is: {0}" -f ($Ohio |
Measure-Object -Average).Average

"Average StateUpNorth IQ after move is: {0}" -f ($StateUpNorth |
Measure-Object -Average).Average



Enjoy!

Wednesday, January 18, 2012

More Training Questions: Connect to different domain

At a recent internal PowerShell training session, I was asked how to connect to a different domain. Following are a couple ways to accomplish this (using Quest cmdlets or the ActiveDirectory Module):

# Quest cmdlets
Add-PSSnapin Quest.ActiveRoles.ADManagement
$cred = Get-Credential 'ExtDomain.Local\FatBeard'
Connect-QADService -Service ExtDomain.Local -Cred $cred

Get-QADUser

# Active Directory Module
Import-Module ActiveDirectory
New-PSDrive –Name ExtDomain
–PSProvider ActiveDirectory
–Server ExtDomain.Local
–credential (Get-Credential ‘ExtDomain.Local\FatBeard’)
–root ‘//RootDSE/’

Get-ADUser -filter *

Enjoy!

Training Questions: Date and Numeric Formatting with PowerShell

At a recent internal training session, I was asked how to format dates in PowerShell. A little later I was asked how to format numbers, and later still I was asked if it was possible to right justify strings. Following is the example I used to demonstrate the formating capabilities:

Get-ADUser SamAccountName -Properties WhenCreated | 
Select SamAccountName,WhenCreated,
@{Name="ShortDate"; Expression={ "{0:d}" -f $_.WhenCreated} },
@{N="LongDate";E={ "{0:D}" -f $_.WhenCreated} },
@{L="FullDateShortTime";E={ "{0:f}" -f $_.WhenCreated} },
@{N="FullDateLongTime"; E={ "{0:F}" -f $_.WhenCreated} },
@{N="GeneralDateShortTime";E={ "{0:g}" -f $_.WhenCreated} },
@{N="GeneralDateLongTime"; E={ "{0:G}" -f $_.WhenCreated} },
@{N="Month";E={ "{0:M MM MMM MMMM}" -f $_.WhenCreated} },
@{N="Day"; E={ "{0:d dd ddd dddd}" -f $_.WhenCreated} },
@{N="Year"; E={ "{0:y yy yyy yyyy}" -f $_.WhenCreated} },
@{N="Hour"; E={ "{0:h hh H HH}" -f $_.WhenCreated} },
@{N="Minute"; E={ "{0:m mm}" -f $_.WhenCreated} },
@{N="Second"; E={ "{0:s ss}" -f $_.WhenCreated} },
@{N="AM/PM"; E={ "{0:t tt}" -f $_.WhenCreated} },
@{N="CustomDateTime1"; E={ "{0:M/d/yy h:m:s tt}" -f $_.WhenCreated} },
@{N="CustomDateTime2"; E={ "{0:dddd, MMMM yyyy - HH:mm:ss}" -f $_.WhenCreated} }

Get-WmiObject -Class win32_logicalDisk -Filter "DeviceID='C:'" |
Select DeviceID, FreeSpace,
@{N="FreeSpaceDecimal"; E={ "{0:d}" -f $_.FreeSpace} },
@{N="FreeSpaceScientific1"; E={ "{0:E}" -f $_.FreeSpace} },
@{N="FreeSpaceScientific2"; E={ "{0:E1}" -f $_.FreeSpace} },
@{N="FreeSpaceFixed1"; E={ "{0:F1}" -f $_.FreeSpace} },
@{N="FreeSpaceFixed2"; E={ "{0:F5}" -f $_.FreeSpace} },
@{N="FreeSpaceGeneral"; E={ "{0:G3}" -f $_.FreeSpace} },
@{N="FreeSpaceNumber"; E={ "{0:N3}" -f $_.FreeSpace} },
@{N="FreeSpacePercent"; E={ "{0:P}" -f ($_.FreeSpace/$_.Size)} },
@{N="FreeSpaceGB"; E={ "{0:N0} GB" -f ($_.FreeSpace/1GB)} }

$myString = "Yuengling"
"{0,10}" -f $myString
"{0,15}" -f $myString
"{0,20}" -f $myString

Results:


Enjoy!