PowerShell Workflows: A Practical Example

PowerShell Workflows: A Practical Example

  • Comments 4
  • Likes

Summary: Honorary Scripting Guy and PowerShell MVP Richard Siddaway concludes his exciting series on Windows PowerShell workflows with a practical example.

Microsoft Scripting Guy, Ed Wilson, is here. Today, we have the last article in the most excellent Richard Siddaway workflow 8-part series. 

Note The first article, PowerShell Workflows: The Basics, introduced the basic concepts of Windows PowerShell workflow. The second article, PowerShell Workflows: Restrictions, discussed the restrictions encountered with working with Windows PowerShell workflows. The third article was PowerShell Workflows: Nesting. The fourth article talked about PowerShell Workflows: Job Engine. The fifth article talked about PowerShell Workflows: Restarting the Computer. Next, was PowerShell Workflow: Using Parameters. And the last published article was PowerShell Workflows: Design Considerations. You should read these articles before getting into today’s article.

Once again, here is Honorary Scripting Guy and Windows PowerShell MVP Richard Siddaway.

Thanks, Ed, and thank you for the opportunity to put this series of articles together.

This is the last article of this series on Windows PowerShell workflows but by no means the last word on the subject. Workflows is a new concept with Windows PowerShell and, like all new concepts, it needs to be tested and experimented with to determine where it best fits into our administrator’s toolbox.

For this article, I thought I’d pull together the various strands of the series and present the development of a workflow. The scenario involves an organization that is expanding. It keeps creating new offices and needs to automate as much as possible of the IT administration associated with those new offices coming online. The list of activities that has to be performed includes:

  1. Create OU – parent OU for location
    1. Create Users OU as child
    2. Create Computers OU as child
  2. Create UPN
  3. Create accounts
    1. Create 35 computer accounts in correct OU
    2. Create 35 user accounts in correct OU
    3. Create home drives
  4. Create AD Site
    1. Create AD subnet & link to site
    2. Create AD Site Link
  5. Link GPOs
    1. Link GPO to Users OU
    2. Link GPO to Computers OU

The first and most important point is that this doesn’t need to be done as a workflow. I’d be surprised if there is any task that has to be performed as a workflow. It may be more complicated as a suite of scripts, but it will be possible. Having said that, we will use a workflow.

The big thing that workflows bring is parallelism, so you need to decide what can be done in parallel and what must be done sequentially. The other factor for the design is the order in which things happen, for instance, you need to create the OUs before you create the user accounts. 

Programmers have a concept called pseudo-code. It is used to map the logic of a piece of code under development. It’s not something that many scripters use, but for complicated situations like this, it can save a lot of rework.

If you turn the list of requirements into pseudo-code, you get something like this:

workflow pseudocode {

 parallel {

  create parent OU

  create UPN

  create AD site

 }

 parallel {

  sequence {

   create users OU

   read user data

   parallel {

    create user accounts

    create home drives

   }

   link GPO to users OU

  }

  sequence {

   create computers OU

   read computer data

   parallel {

    create computer accounts

   }

   link GPO to computers OU

  }

  sequence {

   create AD subnet

   create AD site link

  }

 }

}

Start with a parallel block to create the top OU, AD site, and UPN. These can be performed in parallel because there is no interaction between them. The bulk of the workflow occupies a parallel block that contains three sequence blocks—one for user accounts, one for computer accounts, and one for the remaining Active Directory topology. Within the sequence blocks, there is some scope for performing tasks—such as creating users—in parallel.

There are other possible ways to group these tasks—this is the way that it works for me. The workflow after some development and testing comes out like this:

workflow new-location {

param (

 [string]$locationname,

 [string]$subnet

)

 parallel {

  New-ADOrganizationalUnit -Path "DC=manticore,DC=org" -Name $locationname

  Set-ADObject -Identity "CN=Partitions,CN=Configuration,DC=manticore,DC=org" -Add @{upnsuffixes = "$locationname-manticore.org" }

  New-ADReplicationSite -Name $locationname

 }

 parallel {

  sequence {

   New-ADOrganizationalUnit -Path "OU=$locationname,DC=manticore,DC=org" -Name "Users"

   ##

   ## read user data

   $password = ConvertTo-SecureString -AsPlainText -String "Pa55W0rd1!" -Force

   $users = Import-Csv  -Path  ./userdata.csv

   ##

   ## create users & home drives

   foreach -parallel ($user in $users){

     New-ADUser -AccountPassword $password  -Name ($user.Lname + " " + $user.Fname) -Path "OU=Users,OU=$locationname,DC=manticore,DC=org" -SamAccountName $user.id -UserPrincipalName "$user.id@$locationname-manticore.org"

     New-Item -Path "\\w12standard\home"  -Name $user.id -ItemType Directory

   }

   ##

   ## link GPO to users OU

   New-GPLink -Name "Management Configuration" -Target "OU=Users,OU=$locationname,DC=manticore,DC=org"

  }  

  ##

  ## create Computers OU, computers & link GPO

  sequence {

   New-ADOrganizationalUnit -Path "OU=$locationname,DC=manticore,DC=org" -Name "Computers"

   $computers = 1..35

   foreach -parallel ($computer in $computers) {

    $cname = "$locationname-{0:000}"  -f $computer

    New-ADComputer -Path "OU=Computers,OU=$locationname,DC=manticore,DC=org" -Name $cname

   }

   New-GPLink -Name "W12Updates" -Target "OU=Computers,OU=$locationname,DC=manticore,DC=org"

  }

  sequence {

   New-ADReplicationSubnet -Name $subnet -Site $locationname

   New-ADReplicationSiteLink -Name "Site1-$locationname" -SitesIncluded "Site1", $locationname -InterSiteTransportProtocol IP -ReplicationFrequencyInMinutes 15

  }

 }

}

Two parameters are required—a location name and a subnet. The workflow can be used like this:

new-location -locationname Seattle -subnet "10.10.19.0/24"

The first step in the workflow is to create an OU for the location, an AD site, and a UPN.

parallel {

  New-ADOrganizationalUnit -Path "DC=manticore,DC=org" -Name $locationname

  Set-ADObject -Identity "CN=Partitions,CN=Configuration,DC=manticore,DC=org" -Add @{upnsuffixes = "$locationname-manticore.org" }

  New-ADReplicationSite -Name $locationname

 }

These tasks can be performed in parallel. There are no links or dependencies between the three tasks, so you don’t care about processing order. The New-ADReplicationSite cmdlet is new in Windows Server 2012. If you don’t have access to the cmdlet, you can modify this script:

$for = [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest()

$fortyp = [System.DirectoryServices.ActiveDirectory.DirectoryContexttype]"forest"

$forcntxt = new-object System.DirectoryServices.ActiveDirectory.DirectoryContext($fortyp, $for)

## create the site

$site = new-object System.DirectoryServices.ActiveDirectory.ActiveDirectorySite($forcntxt, "MyNewSite2")

$site.Save()

You will need to run it as an InlineScript.

Moving into the bulk of the script, you have three sequence blocks that are processed in parallel. This where the real power of a workflow lies—you can create user accounts; computer accounts, and modify the Active Directory topology all in one pass. The first sequence block creates the user accounts:

  sequence {

   New-ADOrganizationalUnit -Path "OU=$locationname,DC=manticore,DC=org" -Name "Users"

   ##

   ## read user data

   $password = ConvertTo-SecureString -AsPlainText -String "Pa55W0rd1!" -Force

   $users = Import-Csv  -Path  ./userdata.csv

   ##

   ## create users & home drives

   foreach -parallel ($user in $users){

     New-ADUser -AccountPassword $password  -Name ($user.Lname + " " + $user.Fname)

    -Path "OU=Users,OU=$locationname,DC=manticore,DC=org" -SamAccountName $user.id

    -UserPrincipalName "$user.id@$locationname-manticore.org"

     New-Item -Path "\\w12standard\home"  -Name $user.id -ItemType Directory

   }

   ##

   ## link GPO to users OU

   New-GPLink -Name "Management Configuration" -Target "OU=Users,OU=$locationname,DC=manticore,DC=org"

  }  

An OU is created for the user accounts. The locationname variable is referenced in a number of places. You are using the variable that is defined in a higher scope, and as you are working with parallel/sequence block, you just need the variable name.

A password is set. No, I wouldn’t do this in production—I’d pass it in as another parameter. Never code passwords in production scripts! The user information is read from a CSV file. In this case, the file contains first name; last name, and logon ID. Other data can be easily added and other parameters on the New-ADUser cmdlet utilized. If you have not used that cmdlet, I recommend looking at the Help file to see what is available. One oddity is the way I’ve had to create the name of the user account—pure string substitution didn’t work and you can’t use subexpressions in workflows.

I’ve hard-coded the file name, but the workflow could be modified to have the location name as part of the work flow. How about the idea of using the Windows PowerShell event engine to watch a particular folder. When a new file is dripped into it, the event triggers and runs the workflow! You can’t get much more automated than that!

The user account creation is performed using a foreach –parallel loop. These are freakily powerful. In this case, 35 user accounts created in parallel. The account creation and home drive creation steps are executed in sequence, but the 35 sets of that activity are performed in parallel. Trying to keep track of where the workflow is at a particular time is an interesting job. I think that with something like this it is only possible if you log the individual steps with timestamps.

The final step in this sequence is to link a GPO to the Users OU. 

Next up is creating the computer accounts:

sequence {

   New-ADOrganizationalUnit -Path "OU=$locationname,DC=manticore,DC=org" -Name "Computers"

   $computers = 1..35

   foreach -parallel ($computer in $computers) {

    $cname = "$locationname-{0:000}"  -f $computer

    New-ADComputer -Path "OU=Computers,OU=$locationname,DC=manticore,DC=org" -Name $cname

   }

   New-GPLink -Name "W12Updates" -Target "OU=Computers,OU=$locationname,DC=manticore,DC=org"

  }

An OU is created for the computer accounts. I want to create 35 computers. I’ve hard-coded this for simplicity, but again, this could be a parameter. Alternatively, you could count the number of user accounts you are creating and create one computer account per user.

I’m using string formatting to create the computer name according to the organization’s naming convention. That is passed into New-ADComputer. A foreach –parallel loop is used again to maximize the parallelism of the workflow.

Last step is to link a GPO to the Computers OU.

The final sequence creates the last bits of topology:

sequence {

   New-ADReplicationSubnet -Name $subnet -Site $locationname

   New-ADReplicationSiteLink -Name "Site1-$locationname" -SitesIncluded "Site1", $locationname -InterSiteTransportProtocol IP -ReplicationFrequencyInMinutes 15

  }

These two cmdlets are Windows Server 2012−specific. If you don’t have access to them, you could try modifying these scripts. For the subnet:

## get current forest and set context

$for = [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest()

$fortyp = [System.DirectoryServices.ActiveDirectory.DirectoryContexttype]"forest"

$forcntxt = new-object System.DirectoryServices.ActiveDirectory.DirectoryContext($fortyp, $for)

 

$site = "MyNewSite2"

$subnetlocation = "Building X"

$subnetname = "10.55.0.0/24"

 

## create subnet and link to the site

$subnet = New-Object System.DirectoryServices.ActiveDirectory.ActiveDirectorySubnet($forcntxt, $subnetname, $site)

$Subnet.Location = $subnetlocation

$subnet.Save()

 

For the site link:

## get current forest and set context

$for = [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest()

$fortyp = [System.DirectoryServices.ActiveDirectory.DirectoryContexttype]"forest"

$forcntxt = new-object System.DirectoryServices.ActiveDirectory.DirectoryContext($fortyp, $for)

 

$link = New-Object -TypeName System.DirectoryServices.ActiveDirectory.ActiveDirectorySiteLink -ArgumentList $forcntxt, "MyNewSite3-MyNewSite4"

 

$site1 = [System.DirectoryServices.ActiveDirectory.ActiveDirectorySite]::FindByName($forcntxt, "MyNewSite3")

$site2 = [System.DirectoryServices.ActiveDirectory.ActiveDirectorySite]::FindByName($forcntxt, "MyNewSite4")

 

$link.Sites.Add($site1)

$link.Sites.Add($site2)

 

$link.Cost = 150

$link.ReplicationInterval = "01:00:00"   ## 1 hour   24x7

$link.Save()

 

$linkde = $link.GetDirectoryEntry()

$linkde.Description = "Links sites MyNewSite3 and MyNewSite4"

$linkde.SetInfo()

 

These would have to be used via an Inlinescript block. Remember you will need the “using” keyword to access variables from the higher parts of the workflow.

My hope is that this series of articles has shown you the power of workflows and how to get started using them. They are a very powerful resource that the Window PowerShell community is just starting to understand. This series is a step on that journey to understanding but is by no means the last. I would encourage you to experiment with workflows in your environment and share what you discover with the Windows PowerShell community. I can be contacted via my blog.

I suspect this won’t be my last word on workflows but until then—enjoy!

~Richard

Richard, thank you for a superb effort, a wonderful series, and an awesome amount of really great information on Windows PowerShell workflows.

I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

Ed Wilson, Microsoft Scripting Guy

Your comment has been posted.   Close
Thank you, your comment requires moderation so it may take a while to appear.   Close
Leave a Comment