# Wednesday, December 16, 2009

I was wondering today if it would be possible to create an Update Search Folder in Powershell. Turns out you can but it isn’t as straight forward as you might think. According to the SCCM SDK Search Folders are an instance of the SMS_ObjectContainerNode class. You can find the details about this class here.

I’m using the SCCM.psm1 file by Michael Niehaus with a few modifications. I’ve added code to force asking for credentials and also added a function to create new SCCM object. My version is available at the end of this post. The additional function looks like:

function New-SCCMObject {

    [CmdletBinding()] 
    PARAM 
    ( 
        [Parameter(Position=1)] $class 
    ) 
	
	$staticClass = get-wmiobject -query "SELECT * FROM Meta_Class WHERE __Class = '$class'" -computername $sccmServer -namespace $sccmNamespace -Credential $sccmCredentials
	
	$staticClass.CreateInstance()

}

SMS_ObjectContainerNode has a property called SearchString that controls what the search filters are. I figured the easiest way to understand what should go in the SearchString property would be create a search folder manually and then view the property. Here is my Search Folder:
Screenshot

Here is the code to view the contents of the SearchString Property:

#Get the Update Search Folder and Return the XML.
$searchFolder = Get-SCCMObject -class SMS_ObjectContainerNode -filter "Name='All Current Workstation Updates'"
$searchFolder.Get()

#list the XML
$searchFolder.SearchString

The XML looks like:

	
		
			
				'Product:041e4f9f-3a3d-4f58-8b2f-5e6fe95c4591'
				'Product:558f4bc3-4827-49e1-accf-ea79fd72d4c9'
			
		
		
			
				MS
			
		
		
			
				 = 0
			
		
		
			
				 = 0
			
		
	
Pretty straight forward right?? Well no not really but anyway, here is how to create a search folder:

$server = "server"
$siteCode = "ABCa"

$scriptDir = Split-Path -Path $MyInvocation.MyCommand.Path -Parent
Import-Module $scriptDir\SCCM.psm1

Connect-SCCMServer -serverName $server -siteCode $siteCode

$xml=[xml] @'

	
		
			
				'Product:041e4f9f-3a3d-4f58-8b2f-5e6fe95c4591'
				'Product:558f4bc3-4827-49e1-accf-ea79fd72d4c9'
			
		
		
			
				MS
			
		
		
			
				 = 0
			
		
		
			
				 = 0
			
		
	

'@

#create a new instance of a search folder
$newFolder = New-SCCMObject -class SMS_ObjectContainerNode

#Set the Properties
$newFolder.Name = "Workstation Updates"
$newFolder.ObjectType = 1011 #1011 = SMS_SoftwareUpdate
$newFolder.ParentContainerNodeID = 0
$newFolder.SearchFolder = $true
$newFolder.SearchString = $xml.InnerXML
$newFolder.FolderFlags = 1

#Save the folder
$newFolder.Put()

posted on Wednesday, December 16, 2009 2:00:30 PM (E. Australia Standard Time, UTC+10:00)  #    Comments [0]
# Friday, May 08, 2009

I had a situation last week where a server that had the source shares for my Packages and Drivers had to be decommissioned. This initially seemed like an simple task of updating the source location in each package to the new server. However after realising that there were a lot of packages and about 400 drivers I sought a better way. Powershell to the rescue!

The following block of Powershell gets all the drivers and does a search and replace on the server name.

$drivers = Get-WmiObject SMS_Driver -Namespace root\SMS\site_LNK

$drivers | ?{$_.ContentSourcePath -like "\\creekserver\*"} | `
%{$_.ContentSourcePath = $_.ContentSourcePath -replace "creekserver", "PRI-SCCM-V01"; $_.Put()}

I did the same for packages:

$packages = Get-WmiObject SMS_Package -Namespace root\SMS\site_LNK

$packages | ?{$_.PkgSourcePath -like "*creekserver*"} | `
%{$_.PkgSourcePath = $_.PkgSourcePath -replace "creekserver","PRI-SCCM-V01"; $_.Put()}

$packages | %{$_.RefreshPkgSource}


Enjoy!

posted on Friday, May 08, 2009 10:38:08 AM (E. Australia Standard Time, UTC+10:00)  #    Comments [0]
# 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]