# Wednesday, May 28, 2008

So once you've created a Distribution share with MDT at some point you're going to need a LAB Deployment point. Creating and updating Deployment Points is managed via [Microsoft.BDD.ConfigManager.Manager] in Microsoft.BDD.ConfigManager.dll.

To get stared you need need a DeployManager:
[System.Reflection.Assembly]::LoadFile("C:\Program Files\Microsoft Deployment Toolkit\Bin\Microsoft.BDD.ConfigManager.dll") | Out-Null
$manager = [Microsoft.BDD.ConfigManager.Manager]
$deployManager = $manager::DeployManager

Once you've got a DeployManager you can create a new deployment point using CreateNew(). This returns a generic deployment point which you can then specify what type and settings you need for the deployment point. Specifying the type and settings (ie. all the options on the General, Rules, WinPE tabs) is done by setting some properties on the deployment point object like so:

$deploymentPoint = $deployManager.CreateNew()
$deploymentPoint["Name"] = "LAB"
$deploymentPoint["Type"] = "LAB"
$deploymentPoint["NetworkShare"] = $NetworkShare
$deploymentPoint
["LocalSharePath"] = $LocalSharePath
$deploymentPoint
["GenerateLiteTouchISO"] = $true
$deploymentPoint["GenerateLiteTouchFlatISO"] = $false
$deploymentPoint["GenerateGenericISO"] = $false
$deploymentPoint["GenerateGenericFlatISO"] = $false

This is not a complete list of properties but it gives you the right idea. Once you have a deployment point you also need to specify which Applications, Task Sequences and Driver Groups you wish to include. For a LAB deployment point this is all of them:

$applicationRow = $deploymentPoint.CreateNewChild("Application")
$applicationRow["Application_Column"] = "*"
$deploymentPoint.AppendChild("Application", $applicationRow)

$taskSequenceRow = $deploymentPoint.CreateNewChild("TaskSequence")
$taskSequenceRow["TaskSequence_Column"] = "*"
$deploymentPoint.AppendChild("TaskSequence", $taskSequenceRow)

$driverGroupRow = $deploymentPoint.CreateNewChild("DriverGroup")
$driverGroupRow["DriverGroup_Column"] = "*"
$deploymentPoint.AppendChild("DriverGroup", $driverGroupRow)

After we've made all the changes we want you need to commit all the changes:
$deploymentPoint.Update()

The other really nice part about using MDT from powershell is the Bootstrap and CustomSettings writers that are built in. For example, if I want to add some lines to Bootstrap and CustomSettings.ini for the deployment point I've just created:

#Update the Bootstrap.ini file
$deploymentPoint.Bootstrap.Write("Default", "DeployRoot", $NetworkShare)
$deploymentPoint
.Bootstrap.Write("Default", "KeyboardLocale", "en-AU")
$deploymentPoint
.Bootstrap.Write("Default", "SkipBDDWelcome", "YES")

#Write Some default values to the CustomSettings.ini file
$csIni = $deploymentPoint.CustomSettings
$csIni.Write("Default", "_SMSTSORGNAME", "Windows XP Build Process")
$csIni.Write("Default", "UserDomain", "LAB")
$csIni.Write("Default", "KeyboardLocale", "en-AU")
$csIni.Write("Default", "SkipBDDWelcome", "YES")

For an end-to-end example have a look at Create-LABDeploymentPoint.zip (1.4 KB). This script sets up a lab deployment point and sets a bunch of settings that I use for XP deployments. If you want to test it out without breaking your existing LAB deployment point change line 23 to LAB_TEST.

posted on Wednesday, May 28, 2008 10:48:43 AM (E. Australia Standard Time, UTC+10:00)  #    Comments [0]
# Tuesday, April 29, 2008

I've been playing with automating MDT 2008 using Powershell and thought I'd start to share some of the info over the next few weeks.

The very first thing you need to do in MDT is create a Distribution Share. It turns out this is exceptionally easy in Powershell. The Microsoft.BDD.ConfigManager.Manager namespace has a method called UpgradeDistributionShare(string location, bool update). Just pass in the location you'd like to create the distribution share at and you're done! You can set update to true if you are upgrading an existing Distribution Share to MDT 2008.

Here is the code:

Param
(
   [
string]$Location =$(throw "You must specify a Location")
)

#Initialize MDT
[System.Reflection.Assembly]::LoadFile("C:\Program Files\Microsoft Deployment Toolkit\Bin\Microsoft.BDD.ConfigManager.dll") | Out-Null
$manager = [Microsoft.BDD.ConfigManager.Manager]

#Create a new MDT Distribution Share
$manager::UpgradeDistributionShare($Location, $false)

posted on Tuesday, April 29, 2008 1:36:55 PM (E. Australia Standard Time, UTC+10:00)  #    Comments [0]
# Friday, September 21, 2007

Last week I needed to add a large number of hotfixes to the Task Sequencer for an XP BDD 2007 build. After adding the first few by hand I quickly sought after an easier method. As I result I came up with the code below. Althougth it may not be that useful to anyone else I thought I'd post it anyway so people can start to see what is possible using Powershell with BDD.

To use this function as is you need to have a Security Updates group in the task sequencer already. Then create a directory called Hotfixes under Applications in your Distribution point. Download the hotfix into the hotfixes directory and then run Add-Hotfix "hotfixname" "Path to TS.xml". So for a hotfix called WindowsXP-KB923191-x86-ENU.exe you would type something like: Add-Hotfix "WindowsXP-KB923191-x86-ENU" "C:\Data\TS.xml"

function Add-Hotfix ([string] $hotfix, [string]$TSFile) {
   $ts = [xml](Get-Content -read -1 $TSFile)
   $updates = $ts.psbase.SelectSingleNode("//group[@name='Security Updates']")

   $step = $ts.CreateElement("step")

   $name = $ts.CreateAttribute("name")
   $name.psbase.Value = "Microsoft Update - " + $hotfix
   $step.SetAttributeNode($name)

   $disable = $ts.CreateAttribute("disable")
   $disable.psbase.Value = "false"
   $step.SetAttributeNode($disable)

   $continueOnError = $ts.CreateAttribute("continueOnError")
   $continueOnError.psbase.Value = "false"
   $step.SetAttributeNode($continueOnError)

   $successCodeList
= $ts.CreateAttribute("successCodeList")
   $successCodeList.psbase.Value = "0 3010"
   $step.SetAttributeNode($successCodeList)

   $description
= $ts.CreateAttribute("description")
   $description.psbase.Value = ""
   $step.SetAttributeNode($description)
   
   $startIn
= $ts.CreateAttribute("startIn")
   $startIn.psbase.Value = "%DEPLOYROOT%\Applications\Hotfixes"$step.SetAttributeNode($startIn)

   $action
= $ts.CreateElement("action")
   $action.psbase.InnerText = $hotfix + ".exe /quiet /passive /norestart"
   $step.AppendChild($action)

   $updates
.AppendChild($step)
   
   $ts
.Save($TSFile)
}

posted on Friday, September 21, 2007 1:56:09 PM (E. Australia Standard Time, UTC+10:00)  #    Comments [0]
# Monday, July 30, 2007

Today I found myself clicking through a large number of text files containing information from Cisco switches. The text files contained a show run, show ver, show cdp neighbours and show interface status.

The customer wanted to know which interfaces had been hard-coded for speed or duplex and which interfaces had auto-negotiated to half duplex. After doing the first two by hand I realized Powershell could accomplish this quite quickly.

Get-ChildItem -Recurse -Filter "show-run.txt" | Select-String "speed","duplex"

And the results:
172.16.3.101\show-run.txt:50: duplex half
172.16.3.101\show-run.txt:51: speed 100
172.16.3.103\show-run.txt:35: speed 10
172.16.3.103\show-run.txt:45: duplex half
172.16.3.103\show-run.txt:46: speed 10
172.16.3.103\show-run.txt:98: duplex full
172.16.3.103\show-run.txt:99: speed 100
172.16.3.166\show-run.txt:41: duplex half
172.16.3.166\show-run.txt:42: speed 100

To find the interfaces that have negotiated at half duplex:
Get-ChildItem -Recurse -Filter "show-int.txt" | Select-String "half"

The Results:
172.16.3.101\show-int.txt:10:Fa0/6                      notconnect   1          Half     100 100BaseTX/FX
172.16.3.101\show-int.txt:21:Fa0/17                     connected    1        A-Half    A-10 100BaseTX/FX
172.16.3.101\show-int.txt:32:Gi0/1                      connected    1        A-Half    1000 CX_GIGASTACK
172.16.3.102\show-int.txt:32:Gi0/1                      connected    1        A-Half    1000 CX_GIGASTACK
172.16.3.103\show-int.txt:6:Fa0/4                      connected    1          Half      10 100BaseTX/FX
172.16.3.106\show-int.txt:5:Fa0/1                      connected    1        A-Half   A-100 100BaseTX/FX
172.16.3.107\show-int.txt:32:Gi0/1                      connected    1        A-Half    1000 CX_GIGASTACK
172.16.3.121\show-int.txt:32:Gi1/1                      connected    1        A-Half    1000 CX_GIGASTACK
172.16.3.123\show-int.txt:32:Gi0/1                      connected    1        A-Half    1000 CX_GIGASTACK
172.16.3.164\show-int.txt:33:Gi0/2                      connected    1        A-Half    1000 CX_GIGASTACK
172.16.3.166\show-int.txt:5:Fa0/1                      notconnect   1          Half     100 100BaseTX/FX
172.16.3.166\show-int.txt:32:Gi0/1                      connected    1        A-Half    1000 CX_GIGASTACK
172.16.3.169\show-int.txt:33:Gi0/2                      connected    1        A-Half    1000 CX_GIGASTACK
176.16.3.122\show-int.txt:31:Fa0/24                     connected    1        A-Half   A-100 100BaseTX/FX
...
Just saved myself half an hour of clicking through txt files!

posted on Monday, July 30, 2007 1:31:30 PM (E. Australia Standard Time, UTC+10:00)  #    Comments [0]
# Monday, April 02, 2007

I find that my free space is one of those things I don't check regularly enough. This morning I logged on only to find my system was really struggling. I didn't take long to realise I only had 600MB of disk space left. Dratt!!

I immediately wondered what was the quickest way to find out where all my space went, Powershell maybe? My first thought was something like this:

get-ChildItem -path C:\ -recurse | where-Object {$_.Length -gt 50000000} | sort-Object -property length -Descending |select-Object -first 10 | format-Table Name, Length -auto

This seemed to work ok but took ages:
Days              : 0
Hours             : 0
Minutes           : 1
Seconds           : 55
Milliseconds      : 828
Ticks             : 1158286885
TotalDays         : 0.00134060982060185
TotalHours        : 0.0321746356944444
TotalMinutes      : 1.93047814166667
TotalSeconds      : 115.8286885
TotalMilliseconds : 115828.6885

Anyone know a better way??

posted on Monday, April 02, 2007 9:59:42 AM (E. Australia Standard Time, UTC+10:00)  #    Comments [0]
# Monday, March 12, 2007

I've been involved in an Active Directory Migration lately and one of the things that was taking a lot of time was finding the groups for a group of users and then finding the nested groups. Why, you might ask? Well in this particular instance it was easier for us to migrate groups first than users but we had to make sure we got all the groups for the users we wanted to migrate moved over first.

The first part of the script involves search Active Directory for the user we wish to find group membership for. This is pretty easy:
#Setup Tasks
$query = new-object system.directoryservices.directorysearcher
$root = [adsi]""

#Setup for Query
$query.searchroot=[adsi]$("LDAP://"+$root.distinguishedName)
$query.Filter = "(&(objectCategory=user)(objectClass=person)(samAccountName=$accountName))"
$query.SearchScope="Subtree"

The next thing to do after we find the user is to get the memberOf Property:
$ADobject.psbase.Properties["memberOf"]

So I found it was easy enough to get a list of groups for a user but how could a get a list for several users without getting duplicates? The logical thing seemed like a hashtable:
$groups = @{}
foreach ( $user in $users ) {
   
foreach ($group in Get-Groups $user
   {
      
$groupName=$($group.split(",")[0]).split("=")[1]
      
if(!$groups.Contains($groupname)){$groups.Add($groupname,$group)}
   }
}

The Properties["memberOf"] returns a collection of distinguished names, (CN=GroupName,OU=Groups,DC=domain,DC=local), so the above code splits up that string to extract the groupName to be the key for the hashtable and then the DN as the value. After this it is simply a case of connecting to each group and listing the memberOf property to see if there is any nested groups.

The code will only check 1 level deep so if you have a chain of nested groups you'll have to check it manually.

To Run the script just run .\GetGroups "username1","username2","username3". Please let me know what you think.

FindNestedGroups.ps1.txt (.91 KB)
posted on Monday, March 12, 2007 9:58:11 AM (E. Australia Standard Time, UTC+10:00)  #    Comments [0]
# Thursday, July 13, 2006

Spotted this little utility the other day called ShinyPower and it is definately a must have for anyone learning PowerShell. It's over at secretGeek: http://secretgeek.net/shinyPower.asp

From the site: "ShinyPower is a little C# app i wrote to automate browsing PowerShell's help files."

posted on Thursday, July 13, 2006 3:04:03 PM (E. Australia Standard Time, UTC+10:00)  #    Comments [0]