Tag Archives: FridayFun

Friday Fun – Get Happy Holiday

Today’s Friday Fun is my version of a script originally posted a few years ago by The PowerShell Guy. I’ll list the code but you really need to run it to enjoy it.


#requires -version 2.0

#This must be run from the PowerShell console. NOT in the ISE.

#this is based on a script originally posted by The PowerShell Guy
#http://thepowershellguy.com/blogs/posh/default.aspx

$Greeting = "$([char]14) **Happy Holidays** $([char]14)"

Clear-Host
Write-host "`n"
$Peek = " ^ "
$tree = "/|\"
$i = 20
$pos = $host.ui.rawui.CursorPosition
#adjust to center the display
$offset = ($host.ui.rawui.WindowSize.Width - 72)/2

write-host -fore 'red' ($peek.PadLeft($i-1).PadRight(36) * 2)
write-host -fore 'green' ($tree.PadLeft($i-1).PadRight(36) * 2)

1..16 | Foreach {
#build out the tree
$tree = $tree -replace "/(.*)\\",'//$1\\'
write-host -fore 'green' ($tree.PadLeft($i).PadRight(36) * 2)
$i++
}

write-host -fore 'green' ("|||".PadLeft(19).PadRight(36) *2 )
write-host -fore 'green' ("|||".PadLeft(19).PadRight(36) *2)

$rect = New-Object System.Management.Automation.Host.Rectangle
$rect.top = $pos.y
$rect.Right = 70
$rect.Bottom = $pos.y + 19
$buffer = $host.ui.rawui.getbuffercontents($rect)
#random number object
$R = new-object system.random
$ball = new-object System.Management.Automation.Host.BufferCell
$ball.backgroundColor = $host.ui.rawui.BackgroundColor

1..150 | ForEach {
sleep -Milliseconds 100
#get a random position
$rx = $r.Next(19)
$ry = $r.Next(70)

#define a collection of figures to be used as ornaments
$ornaments = '@','*','#',":"
#get a random ornament
$ball.Character = Get-Random $ornaments
$ball.ForegroundColor = $r.next(16)

if ($buffer[$rx,$ry].Character -eq '/') {$buffer[$rx,$ry] = $ball}
if ($buffer[$rx,$ry].Character -eq '\') {$buffer[$rx,$ry] = $ball}
$host.ui.rawui.SetBufferContents($pos,$buffer)
}

#write the greeting centered
$pos.y = $pos.y + 22
$pos.x = 36 - (($Greeting.Length)/2)
$host.ui.rawui.CursorPosition=$pos

Write-Host $Greeting -Fore 'red'

This is probably the epitome of PowerShell fun: not practical in any way but still done with PowerShell. Enjoy.

Download Get-HappyHoliday

Friday Fun: 13 More Scriptblocks

In celebration of Friday the 13th I thought I would offer up a menu of 13 more script blocks. If you missed the first course, you can find the original 13 scrptblocks here. I’m not going to spend a lot of time going over these. Many of them are simple one liners. Some of them take parameters just like functions and scripts. The easiest way to execute any of the scriptblocks is to use the & operator. But these might also come in handy with any cmdlet that takes a scriptblock as a parameter value such as Invoke-Command.

I think of scriptblocks as “quick and dirty” blocks of re-usable code. If you find something very useful, you might expand it into a full-blown function complete with error handling and verbose output. Or you might find a handy technique in one of these examples.


#1 Get top problem source from the last 500 event log entries
$topprob={Param($log="System") Get-EventLog -LogName $log -newest 500 -entrytype Error |
Group Source -NoElement | Sort Count | Select -last 1}
#&$topprob

#2 Get folder usage by owner in MB
$usage={Param($path=".") dir $path -recurse | Where {-Not $_.PSIsContainer} |
Select Fullname,Length,@{N='Owner';E={($_ | Get-ACL).Owner}} |
Group Owner | Sort Count -descending|
Select Count,Name,@{N='SizeMB';E={(($_.Group | Measure length -sum).sum)/1MB}}
}
#&$usage

#3 get empty event logs
$emptylog={get-eventlog -list | where {$_.entries.count -eq 0}}
#&$emptylog

#4 Get OS Install date
$install={Param($Computername=$env:computername) Get-WmiObject win32_operatingsystem -comp $computername |
select CSName,Caption, @{N="Install";E={$_.ConvertToDateTime($_.InstallDate)}}}
#&$install

#5 Test if running Windows 8
$test8={Param($Computername=$env:computername)
(Get-WmiObject win32_operatingsystem -comp $computername).Caption -match "Windows 8"
}
#&$test8 MyWin8

#6 Test if running PowerShell v3
$testPS3={Param($Computername=$env:computername)
(test-wsman -ComputerName $computername).Productversion -match "Stack: 3.0"}
#&$testPS3 MyWin8

#7 Get code snippets from help examples
$excode={Param($command="get-service") (get-help $command).examples.example | select code}
#&$excode get-process

#8 Count by 13
$countup={Param($count=5) $x=0; For ($i=0;$i -lt $Count; $i++) {$x+=13;$x}}
#&$countup 13

#9 get a 13 character random password
$randpass={ Param($length=13) $chars=[char[]](33..126) ; -join ($chars | get-random -count $length)}
#&$randpass

#10 Test if profile scripts exist
$profileCheck={
$profile | Select @{N="Type";E={"AllUsersAllHosts"}},@{N="Path";E={$_.AllUsersAllHosts}},@{N="Exists";E={Test-Path $_.AllUsersAllHosts}}
$profile | Select @{N="Type";E={"AllUsersCurrentHost"}},@{N="Path";E={$_.AllUsersCurrentHost}},@{N="Exists";E={Test-Path $_.AllUsersCurrentHost}}
$profile | Select @{N="Type";E={"CurrentUsersAllHosts"}},@{N="Path";E={$_.CurrentUserAllHosts}},@{N="Exists";E={Test-Path $_.CurrentUserAllHosts}}
$profile | Select @{N="Type";E={"CurrentUserCurrentHost"}},@{N="Path";E={$_.CurrentUserCurrentHost}},@{N="Exists";E={Test-Path $_.CurrentUserCurrentHost}}
}
#&$profileCheck | format-list

#11 get default printer
$defaultPrint={get-wmiobject win32_printer -filter "Default='True'"}
#&$defaultPrint

#12 get timezone
$tz={Param($computername=$env:computername) Get-WmiObject win32_timezone -computername $computername |
Select @{N="Computername";E={$_.__SERVER}},Description}
#&$tz

#13 find expired certificates
$expired={dir cert:\ -recurse | where {$_.NotAfter -AND $_.NotAfter -lt (Get-date)}}
#&$expired
#Invoke-command $expired -comp server01

Download the script file and watch out for black cats under ladders!

Friday Fun: Get Next Available Drive Letter

A few days ago I saw on question, I think on Facebook, about using PowerShell to find the next available drive letter that could be used for mapping a network drive. Before I show you my approach, let me state that if you need to map a drive in PowerShell for use only within your PowerShell solution, you don’t need a drive letter. You can name the drive anything you want when using New-PSDrive.


new-psdrive Backup -PSProvider Filesystem -Root "\\NAS01\backup"

But of course that won’t work if you plan on using the NET USE command. So let’s figure out how to determine the next available drive letter. First, let’s build an array of possible drive letters. I’m too lazy to type the letters C-Z (skipping A and B for the sake of nostalgia). So I’ll “create” them like this:


$letters=[char[]](67..90)

This creates an array of letters C-Z, albeit technically [CHAR] objects, but we’re good. Next we need a list of currently used drive letters which we can retrieve from WMI.


$devices=get-wmiobject win32_logicaldisk | select -expand DeviceID

I’m expanding the DeviceID property so $devices is a simple array and not a collection of objects with a DeviceID property. I did that so that I could use the -Contains operator.


$letters | where {$devices -notcontains "$($_):"} | Select -first 1

With this simple command I’m piping the collection of characters to Where-Object, testing if each letter (with the appended 🙂 is NOT found in the array $devices. The letters are already sorted so all I need to do is select the first 1 and that will be the next available drive letter in my current PowerShell session.

Before I let you go today, let me point out one thing: I could combine these two lines into a single PowerShell one-liner.


[char[]](67..90) | Where {(get-wmiobject win32_logicaldisk | select -expand DeviceID) -notcontains "$($_):"} | Select -first 1

I’ll get the same result…BUT…just because you can do something in PowerShell doesn’t mean you should. This one liner takes almost 2400MS to execute. But when I break it into two lines, as I showed you, I get the end result in 110MS which is a dramatic and noticeable difference. You can test for yourself using Measure-Command. The take-away is to not be afraid to use multiple commands, especially in a script. It may be faster and it will certainly be easier to understand.

Friday Fun: A PowerShell Alarm Clock

Today’s Friday Fun is a continuation of my exploration of ways to use Start-Job. A few weeks ago I wrote about using Start-Job to create “scheduled” tasks. I realized I could take this further and turn this into a sort of alarm clock. The goal is to execute at command at a given time, but I wanted to make it easy to specify the time. What I have so far is a function called New-Alarm. I have some other ideas and hope to expand this into a module, but for now I thought I’d toss this out to you and get some feedback.


Function New-Alarm {

[cmdletbinding(SupportsShouldProcess=$True,DefaultParameterSetName="Time")]

Param (
[Parameter(Position=0,ValueFromPipelineByPropertyName=$True)]
[ValidateNotNullorEmpty()]
[string]$Command="Notepad",
[Parameter(Position=1,ValueFromPipelineByPropertyName=$True,ParameterSetName="Time")]
[ValidateNotNullorEmpty()]
[Alias("time")]
[datetime]$Alarm=(Get-Date).AddMinutes(5),
[Parameter(ValueFromPipelineByPropertyName=$True,ParameterSetName="Seconds")]
[int]$Seconds,
[Parameter(ValueFromPipelineByPropertyName=$True,ParameterSetName="Minutes")]
[int]$Minutes,
[Parameter(ValueFromPipelineByPropertyName=$True,ParameterSetName="Hours")]
[int]$Hours,
[Parameter(ValueFromPipelineByPropertyName=$True)]
[Alias("init","is")]
[string]$InitializationScript
)

Process {

if ($seconds) {$Alarm=(Get-Date).AddSeconds($seconds)}
if ($minutes) {$Alarm=(Get-Date).AddMinutes($minutes)}
if ($Hours) {$Alarm=(Get-Date).AddHours($hours)}

Write-Verbose ("{0} Creating an alarm for {1} to execute {2}" -f (Get-Date),$Alarm,$Command)

#define a scriptblock that takes parameters. Parameters are validated in the
#function so we don't need to do it here.
$sbText=@"
Param ([string]`$Command,[datetime]`$Alarm,[string]`$Init)

#define a boolean flag
`$Done=`$False

#loop until the time is greater or equal to the alarm time
#sleeping every 10 seconds
do {
if ((get-date) -ge `$Alarm) {
#run the command
`$ActualTime=Get-Date
Invoke-Expression `$Command
#set the flag to True
`$Done=`$True
}
else {
sleep -Seconds 10
}
} while (-Not `$Done)

#write an alarm summary object which can be retrieved with Receive-Job
New-Object -TypeName PSObject -Property @{
ScheduledTime=`$Alarm
ActualTime=`$ActualTime
Command=`$Command
Initialization=`$Init
}
"@

#append metadata to the scriptblock text so they can be parsed out with Get-Alarm
#to discover information for currently running alarm jobs

$meta=@"

#Alarm Command::$Command
#Alarm Time::$Alarm
#Alarm Init::$InitializationScript
#Alarm Created::$(Get-Date)

"@

#add meta data to scriptblock text
$sbText+=$meta

Write-Debug "Scriptblock text:"
Write-Debug $sbText
Write-Debug "Creating the scriptblock"

#create a scriptblock to use with Start-Job
$sb=$ExecutionContext.InvokeCommand.NewScriptBlock($sbText)

Try {
If ($InitializationScript) {
#turn $initializationscript into a script block
$initsb=$ExecutionContext.InvokeCommand.NewScriptBlock($initializationscript)
Write-Verbose ("{0} Using an initialization script: {1}" -f (Get-Date),$InitializationScript)
}
else {
#no initialization command so create an empty scriptblock
$initsb={}
}

#WhatIf
if ($pscmdlet.ShouldProcess("$command at $Alarm")) {
#create a background job
Start-job -ScriptBlock $sb -ArgumentList @($Command,$Alarm,$InitializationScript) -ErrorAction "Stop" -InitializationScript $Initsb
Write-Verbose ("{0} Alarm Created" -f (Get-Date))
}
}

Catch {
$msg="{0} Exception creating the alarm job. {1}" -f (Get-Date),$_.Exception.Message
Write-Warning $msg
}
} #Process

} #end function

The function includes full help.

To use the function you specify a command string to execute at a given time. The default’s are to run Notepad in 5 minutes. You can either specify an exact time.


PS C:\> new-alarm "get-process | out-file c:\work\noonprocs.txt" -alarm "12:00PM"

Or X number of seconds, minutes or hours.


PS C:\> $s='$f=[system.io.path]::GetTempFilename(); "Hey! Are you paying attention??" > $f;start-process notepad $f -wait;del $f'
PS C:\> new-alarm $s -minutes 15 -verbose

The first command defines a command string, $s. This creates a temporary file, writes some text to it, displays it with Notepad and then deletes it. The second command creates a new alarm that will invoke the expression in 15 minutes.

For now, the command is passed as text. This is so that I can create an internal scriptblock. I use a Do loop to compare the current time to the alarm time. When the time is right, the command string is executed using Invoke-Expression.


$sbText=@"
Param ([string]`$Command,[datetime]`$Alarm,[string]`$Init)

#define a boolean flag
`$Done=`$False

#loop until the time is greater or equal to the alarm time
#sleeping every 10 seconds
do {
if ((get-date) -ge `$Alarm) {
#run the command
`$ActualTime=Get-Date
Invoke-Expression `$Command
#set the flag to True
`$Done=`$True
}
else {
sleep -Seconds 10
}
} while (-Not `$Done)

#write an alarm summary object which can be retrieved with Receive-Job
New-Object -TypeName PSObject -Property @{
ScheduledTime=`$Alarm
ActualTime=`$ActualTime
Command=`$Command
Initialization=`$Init
}
"@

I also add some metadata to the script block which gets written as the job’s result.


#append metadata to the scriptblock text so they can be parsed out with Get-Alarm
#to discover information for currently running alarm jobs

$meta=@"

#Alarm Command::$Command
#Alarm Time::$Alarm
#Alarm Init::$InitializationScript
#Alarm Created::$(Get-Date)

"@

#add meta data to scriptblock text
$sbText+=$meta

Write-Debug "Scriptblock text:"
Write-Debug $sbText
Write-Debug "Creating the scriptblock"

#create a scriptblock to use with Start-Job
$sb=$ExecutionContext.InvokeCommand.NewScriptBlock($sbText)

Finally, the alarm function allows for an initialization command, like you might use with Start-Job. This permits you to run commands such as importing modules or dot sourcing scripts. I have a function that displays a VB style message box. Here’s how I might use it as an alarm job.


PS C:\> new-alarm "get-messagebox 'It is time for that thing' -title 'Alert!'" -init ". c:\scripts\get-messagebox.ps1" -min 5

In 5 minutes the alarm will go off and I’ll get this.

Remember, the function is creating new jobs with the Start-Job cmdlet. Which means I can get job results.


PS C:\> receive-job 7 -keep

Initialization : . c:\scripts\get-messagebox.ps1
ActualTime : 1/20/2012 8:47:07 AM
ScheduledTime : 1/20/2012 8:47:06 AM
Command : get-messagebox 'It is time for that thing' -title 'Alert!'
RunspaceId : d3461b78-11ce-4c84-a8ab-9e3fcd482637

What do you think? As I said, I have a few more ideas and there are certainly a few tweaks I can make even to this code. I’ve added my Get-MessageBox function in case you want to toy with that. Download AlarmScripts.zip and let me know what you think.

Friday Fun: Output to 2 Places in 1

Today’s Friday Fun comes out of a short exchange I had yesterday with Hal Rottenberg on Google Plus. We were playing around with piping a PowerShell command to Clip.exe which dumps the output to the Windows Clipboard. I got to thinking about taking this a step further based on my needs as a writer. Often I’d like to see the results of a command and then copy and paste the results into whatever I’m working on. In other words, I need to TEE the output to two places.

PowerShell has a cmdlet called Tee-Object that follows this principal. The default behavior is to write output to the pipeline AND send it to a text file.


PS C:\> get-service | tee c:\work\svc.txt

I’ll see the results and save them to a text file. I can also use this cmdlet to save results to a variable.


PS C:\> get-service | tee c:\work\svc.txt

Status Name DisplayName
------ ---- -----------
Running AeLookupSvc Application Experience
Stopped ALG Application Layer Gateway Service
Stopped AppIDSvc Application Identity
Stopped Appinfo Application Information
...
Running wudfsvc Windows Driver Foundation - User-mo...
Stopped WwanSvc WWAN AutoConfig

PS C:\> $svc.count
196
PS C:\>

One approach I came up with to incorporate with Clip.exe was this:


PS C:\> get-service | tee -Variable svc | clip

I don’t get the results immediately to the screen; they are saved to the variable. But at the same time output has been directed to the Windows Clipboard. That could be useful. But you know me, I always have to tinker a bit more and I ended up with a function called Out-Tee.


Function Out-Tee {

[cmdletbinding()]

Param (
[Parameter(Position=0,Mandatory=$True,ValueFromPipeline=$True)]
[object[]]$InputObject,
[alias("foregroundcolor","fg")]
[string]$TextColor=$host.ui.rawui.ForegroundColor
)

Begin {
#define an empty array to hold piped in objects
$a=@()
}

Process {
#add each piped in object to the array
$a+=$inputobject
}

End {
#write the array to the pipeline as a string then pass to Write-Host
$a | out-string | write-host -fore $textColor
#write the array again to Clip.exe
$a | clip
}

} #end function

This simple function takes a PowerShell expression and writes the results to the console using Write-Host and also to the clipboard. The default output will use the current console foreground color. But you can specify any other color that you would use with Write-Host. I even added some alias properties so you can use -foregroundcolor or -fg.

With this function I can see the result and have it dumped to the clipboard. Because the default text color is the same as my session, I don’t see any difference when using Out-Tee.


PS C:\> ps | where {$_.ws -gt 100mb} | out-tee

Or if I want to pretty it up, I can add a little color.


PS C:\> ps | where {$_.ws -gt 100mb} | out-tee -TextColor green

Of course, the clipboard is just text. But now I have something easier to use to save output to the clipboard so I can paste it into my documents, assuming I like the output I see on the screen. The one caveat is that this function only works with successful commands. Errors, warnings, or verbose statements won’t get dumped to the clipboard. I can think of some ways around that which I might try in a future version. But for my immediate needs this works just fine.

Download Out-Tee and give it a try.