Working back in the past: making OCS 2007 PowerShell tools in a Version 1.0 World

Recently, I had to work on an environment with PowerShell v. 1.0 (and I couldn’t upgrade it!) and Microsoft Office Communications Server 2007 (or OCS for those of us in the know).
Unfortunately for me, this became a journey of pain and discovery as I dove deep back into the dark annals of PowerShell history.
Here is a short list of things that did not work:
- My modern day approach to tool writing includes throwing in Confirm and WhatIf as a matter of course, especially with potentially harmful actions like stripping OCS attributes from an account. WhatIf and $PSCmdlett don’t exist!
- I like to set my tools up to do one of the following actions: create, set, process, report on particular types of objects. This means that I make liberal use of [Parameter] declarations relating to Pipeline input and aliases, all standard advanced functions nowadays. Advanced functions weren’t added until PS version 2.0!
- I think it is due dilligence to make help and documentation for our tools, especially if they may outlive you in an environment. So for each of my functions, I added Comment-Based help. Guess what? Comment-based help wasn’t supported until PS version 2.0!
- Comment blocks? Nope, Version 2.0, so I hope you like Batch style walls of hash signs.
- [CmdletBinding] for -Debug or -Whatif? Nope, 2.0.
- Running remotely? Nah! Version 2.0 introduced this feature, so enjoy running these commands locally, or tunneled through PSExec.
In the end, the functions I was left with worked, but felt very…very basic. In any case, here are a set of tools to help you enable, process and disable OCS users with PowerShell.
Note that you need to run these commands from an OCS server itself if you’re running PS 1.0. If you’re on a newer version, feel free to adapt these to work with you.
Big credit to this blog post for showing me the original WMI class to query for OCS info - http://blog.insideocs.com/2009/01/16/189/
####################################################################
# ----Name : OCS 2007 Account Manipulation tools
# ---Author: Stephen Owen, 10/3/2014
# ----Ver 1: Release Version, all basic features provided but unfortunately advanced features like true whatif and true pipeline support won't work due to PowerShell limitations
# Function : A set of tools based on this blog post to speed up Enabling and Disabling OCS accounts en masse - http://blog.insideocs.com/2009/01/16/189/
# ---Usage : Use Get-OCSUser to obtain a list of all user accounts in OCS, filter this using Where-Object commands or comparisons and pipe into Disable or Enable-OCSuser Cmdlettes
##########################################
Function Get-OcsUser{
#.Synopsis
# A simpler wrapper for Get-WMIObject on the Microsoft SIPEUser Class
#.DESCRIPTION
# Long description
#.EXAMPLE
# To obtain a list of all OCS Users
#
# Get-OCSUser -Fi;;
#.EXAMPLE
# To obtain only the users from OCS which match those found in a text file
#
# Get-OCSUser | Where { (Get-Content C:\temp\DisableOCS.txt) -contains $_.DisplayName}
param([switch]$Full)
if ($Full){
Get-WmiObject -class MSFT_SIPESUserSetting
}
else{
Get-WmiObject -class MSFT_SIPESUserSetting -Property DisplayName,Enabled,EnabledForEnhancedPresence,EnabledForFederation,UserCategory,PrimaryURI,InstanceID | Select DisplayName,Enabled,EnabledForEnhancedPresence,EnabledForFederation,UserCategory,PrimaryURI,InstanceID
}
}
Function Enable-OcsUser{
##
#.Synopsis
# A wrapper for cretain WMI calls using Get-WMIObject on the Microsoft SIPEUser Class to enable users
##.DESCRIPTION
##Use this wrapper to ease the enablement of OCS Users using PowerShell. Be sure to see the Examples for usage notes, as #PowerShell v1 limitations force some peculiar usage steps.
#.EXAMPLE
## Step 1 Obtain a list of all OCS Users and store in $OCS or a similar string
##
## $OCS = (Get-OcsUser)
##
## Step 2 Verify user to enable
##
## $OCS | Where-Object {$_.PrimaryURI -eq "sip:Testy.McDelete@foxDeploy.com"}
##
## Step 3 ENABLE
##
##$OCS | Where-Object {$_.PrimaryURI -eq "sip:Testy.McDelete@foxDeploy.com"} | ForEach-Object {Enable-OCSUser -PrimaryURI $_.DisplayName}
#.EXAMPLE
## To obtain only the users from OCS which match those found in a text file, then enable them. NOTE the usage of -WhatIf.
##
## Get-OCSUser | Where { (Get-Content C:\temp\DisableOCS.txt) -contains $_.DisplayName} | ForEach-Object {Enable-OCSUser -PrimaryURI $_.DisplayName -WhatIf}
##
Param($PrimaryURI,[switch]$WhatIf)
ForEach ($SIP in $PrimaryURI) {
#get-wmiobject -class MSFT_SIPESUserSetting | where-object { $_.PrimaryURI -eq “sip:userid@SIPDomain” } | % { $_.Enabled = $True; $_.put() | out-null }
if ($WhatIf){
"Safety enablied, Would be enabling $PrimaryURI"
}
ELSE{
"Safety released, actually enabling $SIP"
Get-WmiObject -class MSFT_SIPESUserSetting | Where-Object { $_.DisplayName -eq $SIP } | ForEach-Object { $_.Enabled = $False; $_.put()
Get-OcsUser | Where-Object {$_.DisplayName -eq $SIP}}
}
}
}
Function Disable-OcsUser{
##
#.Synopsis
# A wrapper for cretain WMI calls using Get-WMIObject on the Microsoft SIPEUser Class to disable users
##.DESCRIPTION
##Use this wrapper to ease the deletion of OCS Users using PowerShell. Be sure to see the Examples for usage notes, as #PowerShell v1 limitations force some peculiar usage steps.
#.EXAMPLE
## Step 1 Obtain a list of all OCS Users and store in $OCS or a similar string
##
## $OCS = (Get-OcsUser)
##
## Step 2 Verify user to disable
##
## $OCS | Where-Object {$_.PrimaryURI -eq "sip:Testy.McDelete@foxDeploy.com"}
##
## Step 3 DELETE
##
##$OCS | Where-Object {$_.PrimaryURI -eq "sip:Testy.McDelete@foxDeploy.com"} | ForEach-Object {Disable-OCSUser -PrimaryURI $_.DisplayName}
#.EXAMPLE
## To obtain only the users from OCS which match those found in a text file, then disable them. NOTE the usage of -WhatIf.
##
## Get-OCSUser | Where { (Get-Content C:\temp\DisableOCS.txt) -contains $_.DisplayName} | ForEach-Object {Disable-OCSUser -PrimaryURI $_.DisplayName -WhatIf}
##>
Param($PrimaryURI,[switch]$WhatIf)
ForEach ($SIP in $PrimaryURI) {
if ($WhatIf){
"Would be Disabling $PrimaryURI"
}
ELSE{
"Safety released, disabling $SIP"
Get-WmiObject -Class MSFT_SIPESUserSetting | Where-Object { $_.DisplayName -eq $SIP } | ForEach-Object { $_.Enabled = $False; $_.put()
Get-OcsUser | Where-Object {$_.DisplayName -eq $SIP}
}
}
}
}
One Inch of Power: Bulk file relocation

Hi guys,
Back for another installment of One inch of Power. In this episode, I became sick of seeing my music folder full of folders for albums, each of which may only have a single file or two.
I wrote this one incher to grab all folders with less than three files and move them all into a single folder, for ease of sorting.
"$HOME\Music" | gci |
? PSIsContainer -eq $true | % {
if ((gci $__.FullName -recurse).Count -le 3){
gci $__ -recurse *.mp3 | move-item -Destination $HOME\\Music\\misc
}
}
Here’s the overall flow.
- Get Items from $HOME\Music
- Filter down to only ones where PSIsContainer equals True, which returns only folders
- If the number of items in the folder is less than or equal to three…
- …get a list of all files in the directory
- Move all of those items into $Home\Music\Misc
Pretty amazing what one line of PowerShell can do.
Continue Reading...Slide Deck, photos and resources from my Session at ATLPUG

The Atlanta PowerShell User’s Group first meeting at iVision went off wonderfully! We had a solid turn out, plenty of pizza and soda, and talked a whole lot about PowerShell, what’s new, and whats coming.
Here’s the demo I worked through exploring features using the ISE Props to Mike Robbins for showing me this technique!
#Check our Version, needs to be greater than 5.0. version 9814 is the most up to date version (September preview)
Get-host | Select-Object -Property Version
#region Working with Archives Set-location C:\\Demo
#Something to do with Archives, so lets look for ZIP Get-Command \*-Zip
Get-Command \*-Archive
Compress-Archive -Path C:\\demo -DestinationPath c:\\demo\\Files.zip -CompressionLevel Optimal #mkdir Unzipped
#endregion
#region Modules #Find-module #xActiveDirectory, xJea,
Find-Module
#endregion
#region #ONEGET
#One-Get is Apt-Get come to Windows. It is awesome!
#Import the Module Import-Module OneGet
#List all commands for the module, will prompt to install nuget Get-Command -Module OneGet
#Search your repo's for packages available DONT RUN! $OriginalPackages = Find-Package
#Number of packages Find-Package | Measure-Object | Select -Property Sum
#Not that many apps, where'd they all go? Let's check our package Sources Get-PackageSource
#by default, we only have PSGallery and MSPSGallery as sources...let's add Chocolatey. Previously this was Add-PackageSource, it's changed! $PackageSourceLocation = "http://chocolatey.org/api/v2" Register-PackageSource -ProviderName PSModule -Name Chocolatey -Location $PackageSourceLocation -Trusted -VERBOSE
#After adding Chcolatey DONT RUN! $FullPackages = find-package
#Total count of packages now ~ Find-Package | Measure-Object | Select -Property Sum
find-package evernote
#Search for apps with a summary that mentions PDF find-package | Where Summary -like "\*pdf\*"
find-package evernote | install-package -force #endregion
#region Convert-FromString $TraceRT = tracert -h 6 -w 45 microsoft.com #hops 6, -waiting 45 milliseconds
$TraceRT\[(3..12)\] #Skip the first few lines
$TraceRT\[(3..12)\] -replace "^\\s+" | ConvertFrom-String -PropertyNames Hop,Latency1,Latency2,Latency3,ServerName,ServerIP #Props to Francois Xavier Cat for this regex and general idea
$TraceRT\[(3..10)\] -replace "^\\s+" -replace 'ms','' -replace '\[ \\t\]+$','' | ConvertFrom-String -PropertyNames Hop,Latency1,Latency2,Latency3,ServerName,ServerIP
#endregion
#region DSC Stuff #Remove-item $env:windir\\system32\\MonitoringSoftware -Confirm -Force
Configuration InstallXMLNotePad { param(\[string\[\]\]$MachineName="localhost")
Node $MachineName { File InstallFilesPresent { Ensure = "Present" SourcePath = "\\\\localhost\\Installer" DestinationPath = "C:\\Demo\\InstallFiles" Type = "Directory" Recurse=$true # can only use this guy on a Directory }
Package XMLNotePad { Ensure = "Present" # You can also set Ensure to "Absent" Path = "C:\\Demo\\InstallFiles\\XmlNotepad.msi" Name = "XML Notepad 2007" ProductId = "FC7BACF0-1FFA-4605-B3B4-A66AB382752D" DependsOn= "\[File\]InstallFilesPresent" }
}
}
InstallXMLNotePad
Start-DscConfiguration -Path InstallXMLNotePad -Wait -Verbose -Force #endregion
#Cleanup Demo Unregister-PackageSource -ProviderName PSModule -Name Chocolatey
Here’s the function we created on stage, which recieves and parses output from Trace Route using Convert-FromString.
function Test-Route{ param( $ServerName='microsoft.com', $Hops=6, $Wait=45 )
Write-Host "Tracing Route from localhost to $Servername, capturing $Hops Hops and waiting $Wait MS" $TraceRT = tracert -h $Hops -w $Wait $ServerName #hops 6, -waiting 45 milliseconds
$TraceRT\[(3..10)\] -replace "^\\s+" -replace 'ms','' -replace '\[ \\t\]+$','' | ConvertFrom-String -PropertyNames Hop,Latency1,Latency2,Latency3,ServerName,ServerIP }
Question Time : Why Won't My Script Run?

Accepting Scripts for Review!
I love it when people send me scripts that don’t run, or ask for help when they encounter issues. For me, part of my own learning solidification process is trying to explain things to others. If I can’t explain it to them, then I really don’t know the topic as well as I think I do. If you, dead readers, ever have a script or function that just won’t run, never hesitate to send it to me. I’ll do my best to answer your problem in an understandable and friendly way, and hopefully we’ll both be the wiser. If you’re okay with it, I’ll post it here for others to enjoy too!
-Parameter Binding OMFG
I got an e-mail from a colleague today. He said that this simple script kept failing:
$servers = get-content c:\\temp\\servers.txt Test-connection $servers -count 1 | export-csv C:\\temp\\Server.csv
He sent me a copy of the file, which looked like this:
Running the command would give him (And me too!) the same output!
Invalid parameter, TestConnectionException.Microsoft.PowerShell.Commands
I jumped into the machine, and then saw it for myself, typing out $servers would give me the contents of the file.I could separately ping each of the computers too. But then when I attempted to run the full command, I’d get the same error! Finally, I decided to take a look into the contents of $server, because something was going wrong here…
$servers.Count
>2
Hmm…two items
$servers[1]
>ALBFX3
And the text looks good…what about the length, is something off?
$servers[1].Length
>10
Wait…ALBFX3 is only five letters. So I jumped into the text file itself and…
With Spaces, no one can hear you scream
In the text file, ALBFX3 actually had some spaces in it! PowerShell was getting the second item as ‘ALBFX3 ‘.
PowerShell interprets spaces as the delimiter between arguments and inputs.It thought the spaces were preceding input, and then not finding any, throwing up an ‘Invalid Parameter’ error for that reason. If you run into a bizarro invalid parameter error in the future, remember to double check for trailing spaces.
Continue Reading...Impractical One-liner Challenge
Update
My thanks to Mike F. Robbins and others on reddit who pointed out that the command should be ‘Select -ExpandProperty DistinguishedName’, and not organizationalUnit.
The Challenge
A colleague and I got into a competition earlier today. How could we display an Out-Gridview of all of a companies OU’s, and then move a computer to the selected one in a single line of code.
We condensed our code down to the following two lines:
$destinationOU = Get-ADObject -Filter 'ObjectClass -eq "organizationalUnit"' | Select -ExpandProperty DistinguishedName -Unique | Out-Gridview -passthru
Get-Content .\\Computers.txt | Get-QADObject | Move-QADObject -NewParentContainer $destinationOU -whatif
The goal? Make it into a one-liner.
GUIDELINES:
- Maintain the steps if possible. Minimally acceptable solution:
- Get user input for which OU
- Get a list of computers and move them to the OU
-
NO CHEATING WITH Semicolons or backticks Doing the below doesn’t count
$ou = Out-Gridview;gc Computers.txt | Move-Computer $ou
- Anything else goes!
Please comment here with your answers. I’ll post my own within a few days. Also, if you have any ideas for a future impractical One-Liner Challenge, let me know here, Twitter, or Reddit!
Continue Reading...Adding Autocomplete to your Textbox forms in PowerShell
Today I had a fun little challenge come-up: how do I go about adding Auto-completion records to my forms?
Turns out it is pretty easy! Let’s start very simply. The following will draw out a small box with an OK button, a textbox, and…thats it. Hitting OK will pass along the output of the box.
#Load the assemblies needed for drawing forms with PowerShell
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")
#region to draw the background form
$Form = New-Object System.Windows.Forms.Form
$Form.Text = "Autocompletion Tool"
$Form.Size = New-Object System.Drawing.Size(300,140)
$Form.StartPosition = "CenterScreen"
$Form.KeyPreview = $True
$Form.MaximumSize = $Form.Size
$Form.MinimumSize = $Form.Size
#begin to draw text box
$textbox = New-Object System.Windows.Forms.TextBox
$textbox.Location = New-Object System.Drawing.Size(10,40)
$textbox.Size = New-Object System.Drawing.Size(200,20)
$textbox.Height = 80
$textbox.Name = 'TextBox_UserName'
$Form.Controls.Add($textbox)
#begin to draw an OK button
$OKButton = New-Object System.Windows.Forms.Button
$OKButton.Location = New-Object System.Drawing.Size(220,38)
$OKButton.Size = New-Object System.Drawing.Size(40,25)
$OKButton.Text = "OK"
$OKButton.Add_Click({$xdept=$ListBox.SelectedItem;$xname=$TextBox.Text;$xfname=$TextBoxfName.Text;$Form.Close()})
$Form.Controls.Add($OKButton)
#Make our form topmost, then show it
$Form.Topmost = $True
$Form.Add_Shown({$Form.Activate()})
[void] $Form.ShowDialog()
#Return the value
$textbox.Text
There we go, basic-basic.
The difference is simply adding the following lines starting on line 20:
$textbox.AutoCompleteSource = 'CustomSource' $textbox.AutoCompleteMode='SuggestAppend' $textbox.AutoCompleteCustomSource=$autocomplete
And with that, you can now directly add entries to the AutoCompleteCustomSource using it’s method .AddRange()
You could Import from a file
#Importing from a file
Get-content 'C:\\TEMP\\User Records.txt' | % {$textbox.AutoCompleteCustomSource.AddRange($\_) }
Or you could add individual entries, or the output of a script
#Adding single entries to the inherited AutoCompleteSource object of the textbox
"1","blam","foxdeploy","stephen.owen" | % {$textbox.AutoCompleteCustomSource.AddRange($\_) }
Voila, Magnifique!
As always, feel free to modify for your own purposes, and I hope that this helps you in the future.
Continue Reading...