Trevor Sullivan's Tech Room

Minding the gap between administration and development

Posts Tagged ‘.net’

PowerShell: List Strongly Typed Names in Global Assembly Cache

Posted by Trevor Sullivan on 2011/12/30

I dislike using deprecated commands or APIs when I know that there’s a more modern method of performing an action. I also generally prefer to use Windows PowerShell as a .NET scripting language, rather than constantly relying on cmdlets. To be sure, I use a balance of both concepts, since cmdlets can save a whole lot of coding a lot of the time.

Every time I want to load an assembly into PowerShell, the first thing that pops into my mind is:


Unfortunately Microsoft recommends against using that static method, and recommends use of other methods like:


In the interest of not breaking my conscience, I would like to use this method, but the problem then becomes that I have to constantly figure out what the strongly-typed name of the assembly I want is. To help solve this problem, I decided to write a PowerShell script that extracts information from the .NET assemblies in the Global Assembly Cache (GAC), since those are generally the most common ones I’ll need to reference.

Read the rest of this entry »

Posted in .NET, powershell, scripting, tools | Tagged: , , , , , , , | 6 Comments »

PowerShell: Shortening Web Service Type Names with a Hashtable

Posted by Trevor Sullivan on 2011/07/13

When you use the New-WebServiceProxy class, you probably have noticed that PowerShell dynamically generates some really ugly type names. For example, if we get a reference to the Bing web service (you’ll need to get an API key first):

$BingSearch = New-WebServiceProxy -Class BingSearch -Uri "$ApiKey"

… and examine the types contained within it:

$BingSearch.GetType().Assembly.GetExportedTypes() | select FullName

… you’ll notice some ridiculously long type names based on your API key, such as:


Read the rest of this entry »

Posted in powershell, scripting | Tagged: , , , , , , , , , , , , , , , , | 2 Comments »

PowerShell Quick Tip: Determine STA Threading Mode

Posted by Trevor Sullivan on 2010/11/02

Some code in PowerShell requires you to use the Single-Threaded Apartment (STA) threading model. The PowerShell version 2.0 console host uses Multi-Threaded Apartment by default. The PowerShell Integrated Scripting Environment (ISE) uses the STA threading model.

If you’re writing code using the WPK module included in the PowerShellPack, you will need to ensure that you are using the STA model. You can avoid errors in your code by adding a check for the threading model before you attempt to run WPK code. Here is a simple, one-line example of how to do that:

[System.Management.Automation.Runspaces.Runspace]::DefaultRunspace.ApartmentState -eq [Threading.ApartmentState]::STA

What the above code does is:

  1. Gets the current (default) Runspace
  2. Looks at the ApartmentState property, whose value is an ApartmentState
  3. Compares it to the STA

Note: It’s important to note that, in testing, I discovered that when the threading state is set to MTA in the PowerShell console host, the Runspace.ApartmentState property is set to [Threading.ApartmentState]::Unknown. I don’t know why this is, but keep it in mind if you’re planning on performing a comparison against [Threading.ApartmentState]::MTA – this simply won’t work for the MTA model in PowerShell, unless someone else has a different experience than me.

Posted in .NET, powershell, scripting | Tagged: , , , , , , , , , , , , | 1 Comment »

PowerShell: ConfigMgr WMI Provider (feat. Lazy Properties)

Posted by Trevor Sullivan on 2010/09/28

What are Lazy Properties?

So if you’re a script writer, and you also use System Center Configuration Manager 2007 (ConfigMgr), you may have run into a concept called Lazy Properties. Lazy properties are certain (not all) properties on certain (not all) WMI classes, within the ConfigMgr provider namespace, that are marked with a WMI qualifier denoting the property as “lazy.” That was quite a mouthful! What exactly does this mean? Why are certain properties LAZY? Well, during the development of ConfigMgr, Microsoft realized that the enumeration of some information from the ConfigMgr provider could result in a high load on the server where the provider is installed. They introduced lazy properties as a method of providing only (what they deemed) “essential information” unless the script/software developer specifically requested more information.

How do I identify Lazy Properties?

Now that you know what lazy properties are, you are probably wondering how you’re supposed to know whether or not you have to design your code around lazy properties. This actually isn’t too hard. There are a couple different ways you’ll know that you’re dealing with a lazy property:

  • You’re not getting data back that you’d expect to when querying a ConfigMgr WMI class
  • You find a property marked with the lazy WMI qualifier using a WMI browsing tool (eg. wbemtest, SAPIEN WMI Explorer, CIM Studio, etc.)
  • Run a PowerShell script against the ConfigMgr namespace and look for property definitions with the lazy qualifier — read the last section of this article

Here is a screenshot of WbemTest (WMI utility built into Windows), browsing the class definition of root\sms\site_xyz:SMS_AuthorizationList. Instances of the SMS_AuthorizationList class represent Update List objects in ConfigMgr Software Updates. We can see that the property named “Updates” on this class is marked as lazy. This is just one example, and there are many more properties just like this one.

Ok, so how do I get values from Lazy Properties?

Getting values from lazy properties takes just a couple extra steps than normally working with WMI objects. The key thing to remember with lazy properties is that you have to get a reference to each WMI instance (aka. object) explicitly. You’re still ok to enumerate WMI instances using the SwbemServices.ExecQuery() COM interface in VBscript & PowerShell, or Get-WmiObject in PowerShell, but when you’re iterating over each instance, you must get a second, explicit reference to the WMI instance you’re wanting to work with. Let’s look at an example.

PowerShell Code – Retrieving a Lazy Property

Here is a sample script that retrieves all instances of the SMS_AuthorizationList class, that we examined above. Take note that as we iterate over each instance of SMS_AuthorizationList, we are redefining the $UpdateList variable by retrieving a direct reference to the instance. If we hadn’t done this, the Updates property that we looked at in WbemTest would appear to be empty!

# Retrieve an array of all Update List objects
$UpdateLists = Get-WmiObject -Namespace root\sms\site_lab -Class SMS_AuthorizationList

# Iterate over each Update List object in results
foreach ($UpdateList in $UpdateLists)
    # Using the __PATH property, obtain a direct reference to the instance
    $UpdateList = [wmi]"$($UpdateList.__PATH)"

    # Iterate over each update CI_ID in the Updates array
    foreach ($Update in $UpdateList.Updates)
        # Get a reference to the update we're working with, based on its CI_ID
        $Update = Get-WmiObject -Namespace root\sms\site_lab -Query "select * from SMS_SoftwareUpdate where CI_ID = '$Update'"
        Write-Host "Update List ($($UpdateList.LocalizedDisplayName)) contains update: ",$Update.LocalizedDisplayName

Code Results

Here is a screenshot of Quest’s free PowerGUI Script Editor running the script above, and retrieving a ConfigMgr lazy property (SMS_AuthorizationList.Updates). It’s actually doing a bit more than simply retrieving a WMI instance with its lazy properties — the lazy property (Updates) itself contains only an array of the UpdateIDs (aka. it’s CI_ID) associated to the Update List (SMS_AuthorizationList) object. We’re going one step further and cross-reference the UpdateID with the SMS_SoftwareUpdate class, so we can retrieve more information about the individual updates. This enables us to print out the friendly name of the update, as seen below in the lower-right PowerShell Console window.

What happens if I don’t retrieve lazy properties in my script?

So you’re thinking you can get off easy by just not retrieving lazy properties, right? Well, you can, but you’ll likely run into trouble. The thing is, if you make any changes to your WMI objects, and write them back to the ConfigMgr provider using the Put() method, you’ll effectively erase the values of any lazy properties on that object! It goes without saying that you need to be very careful when making any changes to your ConfigMgr objects via the provider (which is the only way you should be doing it in the first place, programatically).

If you’re simply reading information from the provider, and do not require the information contained within any lazy properties, then you are safe to read the information without performing the additional steps above.

Mega-Bonus Round: Listing All ConfigMgr Lazy Properties

Here’s a very simple PowerShell script we can use to spit out a list of all the lazy properties in the ConfigMgr namespace! Keep in mind that this could change with service pack or “R” revisions to ConfigMgr. I also would surmise that ConfigMgr vNext will introduce more changes to this list, and who knows, maybe they’ll eliminate lazy properties altogether! Just a theory, don’t quote me 🙂

PowerShell Code – Listing All ConfigMgr Lazy Properties

If you copy/paste/execute this code, after changing “lab” to your ConfigMgr site code, you should be able to get similar output to what I’ve included below for a reference. If you’re running the script from a remote management workstation, rather than on your server with the ConfigMgr provider, simply tack on the -ComputerName parameter to the Get-WmiObject cmdlet.

$WmiClassList = Get-WmiObject -List -Namespace root\sms\site_lab
$LazyList = @()

# Iterate over each WMI class in the namespace
foreach ($WmiClass in $WmiClassList)
    # Iterate over properties for current WMI class
    foreach ($WmiProperty in $WmiClass.Properties)
        # Iterate over WMI qualifiers for current WMI property
        foreach ($WmiQualifier in $WmiProperty.Qualifiers)
            # If qualifier is named "lazy", and the value is "true", then it's a lazy property
            if ($WmiQualifier.Name -eq 'lazy')
                # Add class name, property name, and qualifier name to array for processing
                $LazyList += New-Object PSCustomObject -Property @{ "Class" = $WmiClass.__CLASS; "Property" = $WmiProperty.Name; "Qualifier" = $WmiQualifier.Name }

# Sort array of lazy properties by WMI class name, property name
$LazyList = $LazyList | Sort-Object -Property Class,Property
# Write array of lazy properties to console
Format-Table -InputObject $LazyList -Property Class,Property,Qualifier | Out-File "All ConfigMgr Lazy Properties.txt"

ConfigMgr Lazy Property Reference

This is a reference of all ConfigMgr lazy properties, based on an installation of ConfigMgr 2007 SP2 with R3 Release Candidate installed. It’s possible that this list could change, but if you simply run the script above (change your site code from “lab), you can get a current list once again.

Class                                       Property                                    Qualifier
-----                                       --------                                    ---------
SMS_Advertisement                           AssignedSchedule                            lazy
SMS_Advertisement                           AssignedScheduleEnabled                     lazy
SMS_Advertisement                           AssignedScheduleIsGMT                       lazy
SMS_Advertisement                           ExpirationTimeEnabled                       lazy
SMS_Advertisement                           ExpirationTimeIsGMT                         lazy
SMS_Advertisement                           ISVData                                     lazy
SMS_Advertisement                           ISVDataSize                                 lazy
SMS_Advertisement                           PresentTimeEnabled                          lazy
SMS_Advertisement                           PresentTimeIsGMT                            lazy
SMS_AuthorizationList                       LocalizedInformation                        lazy
SMS_AuthorizationList                       Updates                                     lazy
SMS_BootImagePackage                        BackgroundBitmapPath                        lazy
SMS_BootImagePackage                        ContextID                                   lazy
SMS_BootImagePackage                        EnableLabShell                              lazy
SMS_BootImagePackage                        ImageDiskLayout                             lazy
SMS_BootImagePackage                        ImageIndex                                  lazy
SMS_BootImagePackage                        ImageProperty                               lazy
SMS_BootImagePackage                        ReferencedDrivers                           lazy
SMS_CategoryInstanceBase                    LocalizedInformation                        lazy
SMS_CertificateInfo                         Certificate                                 lazy
SMS_CertificateInfo                         PublicKey                                   lazy
SMS_CertificateInfo                         Thumbprint                                  Lazy
SMS_CI_CurrentComplianceStatus              ComplianceStatusDetails                     lazy
SMS_Collection                              CollectionRules                             lazy
SMS_Collection                              RefreshSchedule                             lazy
SMS_Collection                              RefreshType                                 lazy
SMS_Collection                              ReplicateToSubSites                         lazy
SMS_CollectionSettings                      CollectionVariables                         lazy
SMS_CollectionSettings                      PowerConfigs                                lazy
SMS_CollectionSettings                      ServiceWindows                              lazy
SMS_ConfigurationItem                       LocalizedEulas                              lazy
SMS_ConfigurationItem                       LocalizedInformation                        lazy
SMS_ConfigurationItemBaseClass              IsDigest                                    lazy
SMS_ConfigurationItemBaseClass              SDMPackageLocalizedData                     lazy
SMS_ConfigurationItemBaseClass              SDMPackageXML                               lazy
SMS_DeviceSettingItem                       PropList                                    lazy
SMS_DeviceSettingPackage                    DeviceSettingItemUniqueIDs                  lazy
SMS_DistributionPoint                       ISVData                                     lazy
SMS_DistributionPoint                       ISVDataSize                                 lazy
SMS_Driver                                  LocalizedEulas                              lazy
SMS_Driver                                  LocalizedInformation                        lazy
SMS_EULAContent                             EULAText                                    lazy
SMS_G_System_CollectedFile                  FileData                                    lazy
SMS_ImagePackage                            ImageDiskLayout                             lazy
SMS_ImagePackage                            ImageProperty                               lazy
SMS_MachineSettings                         MachineVariables                            lazy
SMS_ObjectContainerNode                     SearchString                                lazy
SMS_OperatingSystemInstallPackage           ImageProperty                               lazy
SMS_PackageBaseclass                        AlternateContentProviders                   lazy
SMS_PackageBaseclass                        ExtendedData                                lazy
SMS_PackageBaseclass                        ExtendedDataSize                            lazy
SMS_PackageBaseclass                        IconSize                                    lazy
SMS_PackageBaseclass                        ISVData                                     lazy
SMS_PackageBaseclass                        ISVDataSize                                 lazy
SMS_PackageBaseclass                        RefreshPkgSourceFlag                        lazy
SMS_PackageBaseclass                        RefreshSchedule                             lazy
SMS_PDF_Package                             Icon                                        lazy
SMS_PDF_Package                             IconSize                                    lazy
SMS_PDF_Package                             RequiredIconNames                           lazy
SMS_PDF_Package                             Status                                      lazy
SMS_PDF_Program                             Icon                                        lazy
SMS_PDF_Program                             IconSize                                    lazy
SMS_PendingRegistrationRecord               Certificate                                 lazy
SMS_PendingRegistrationRecord               PublicKey                                   lazy
SMS_PendingRegistrationRecord               Thumbprint                                  Lazy
SMS_Program                                 ExtendedData                                lazy
SMS_Program                                 ExtendedDataSize                            lazy
SMS_Program                                 IconSize                                    lazy
SMS_Program                                 ISVData                                     lazy
SMS_Program                                 ISVDataSize                                 lazy
SMS_Program                                 SupportedOperatingSystems                   lazy
SMS_Report                                  DrillThroughReportPath                      lazy
SMS_Report                                  ReportParams                                lazy
SMS_Report                                  SQLQuery                                    lazy
SMS_SiteControlFile                         BuildNumber                                 lazy
SMS_SiteControlFile                         FormatVersion                               lazy
SMS_SiteControlFile                         SCFData                                     lazy
SMS_SiteInstallMap                          BuildNumber                                 lazy
SMS_SiteInstallMap                          FormatVersion                               lazy
SMS_SiteInstallMap                          IMapData                                    lazy
SMS_SoftwareUpdate                          LocalizedEulas                              lazy
SMS_SoftwareUpdate                          LocalizedInformation                        lazy
SMS_SoftwareUpdateSource                    PublicKeys                                  Lazy
SMS_StateMigration                          UserNames                                   lazy
SMS_TaskSequencePackage                     BootImageID                                 lazy
SMS_TaskSequencePackage                     Category                                    lazy
SMS_TaskSequencePackage                     CustomProgressMsg                           lazy
SMS_TaskSequencePackage                     DependentProgram                            lazy
SMS_TaskSequencePackage                     References                                  lazy
SMS_TaskSequencePackage                     Reserved                                    lazy
SMS_TaskSequencePackage                     Sequence                                    lazy
SMS_TaskSequencePackage                     SupportedOperatingSystems                   lazy
SMS_TaskSequencePackage                     TaskSequenceFlags                           lazy
SMS_TaskSequencePackage                     Type                                        lazy
SMS_Template                                Data                                        lazy
SMS_UpdatesAssignment                       LegacyCollectInventory                      lazy
SMS_UpdatesAssignment                       LegacyDeploymentSchedule                    lazy
SMS_UpdatesAssignment                       LegacyDPLocality                            lazy
SMS_UpdatesAssignment                       LegacyForceReboot                           lazy
SMS_UpdatesAssignment                       LegacyInstallAllOnDeadline                  lazy
SMS_UpdatesAssignment                       LegacyInstallAllowedWindow                  lazy
SMS_UpdatesAssignment                       LegacyPostponeInstall                       lazy
SMS_UpdatesAssignment                       LegacySilentInstall                         lazy
SMS_VirtualApp                              IconSize                                    lazy

Posted in .NET, configmgr, powershell, scripting, tools, vbscript, wmi | Tagged: , , , , , , , , , , , , | Leave a Comment »

Windows API Code Pack

Posted by Trevor Sullivan on 2010/04/06

There is some free code available on the MSDN Code Gallery, called the Windows API Code Pack for Microsoft .NET Framework, which enables .NET developers to access certain Windows 7 features that are not typically available via the .NET Base Class Library (BCL). Naturally, since this is .NET code, the same functionality ought to be available to PowerShell users!

Get it here:

Among the feature set is:

  • Windows 7 Taskbar – Jump lists, icon overlay, progress bar, tabbed thumbnails, and thumbnail toolbars
  • Windows Shell
  • DirectX
  • Power Management APIs
  • Application Restart and Recovery APIs
  • … and much more!

Hope this helps!

Posted in powershell, scripting, tools | Tagged: , , , , , | Leave a Comment »

Uploading a Video to Youtube via PowerShell

Posted by Trevor Sullivan on 2010/03/09

Hey guys! I know it’s been a long time since I have posted anything useful (that is, assuming anything I post is useful! haha). While sitting at Starbucks today, I suddenly came up with an idea for a new post though, and after doing a quick bit of research, figured I would write about it! The purpose of today’s post is to show an example of using PowerShell to upload a video to Youtube! This could be useful in a variety of circumstances, but I’ll leave finding the use cases up to each of you individually 🙂 A couple of examples I can think of would be:

  • Batch uploading a folder of videos
  • Automating the upload of videos that may be somehow generated into a folder automatically
  • …. ???

In any event, in my search to find a solution, knowing that PowerShell is based on the Microsoft .NET Framework, I searched for “C# upload youtube”. One of the first results that came up was a blog called “Trails in the Sand,” and more specifically, a post entitled “Programmatically Uploading Videos to Youtube.” This blog article goes into detail about uploading videos to Youtube using C#/.NET, but at the bottom of the post, the author was kind enough to include the source code for a library he had written! As a PowerShell scripter, you’ve probably learned to be very thankful for other people who write and share .NET libraries to do “cool stuff!”

After downloading the “Trails in the Sand Youtube Library,” I opened it up in Visual C# 2008 Express Edition (free, fully-functional version of Visual Studio), and converted the project to Visual Studio 2008 format, using the wizard that pops up. The conversion was successful, so I proceeded to create a developer key for Youtube. You can generate your own developer key at the following URL:

After pasting my developer key into the project under the “devCode” string constant, I compiled the library by hitting “F6.” This created my library under the “bin\release” folder of the project. Next, I opened Quest’s PowerGUI (a great, free PowerShell editor), and started a new script. First things first, using the System.Reflection.Assembly .NET class, I loaded the newly created .NET Youtube library into PowerShell:


Next, I instantiated the Trails.YouTube object:

$Yt = New-Object Trails.YouTube

Once I had the YouTube object, I defined a string variable to use as an “out” parameter, to capture any resulting error (if you don’t pre-define it, you’ll get an error):

$UploadError = “”

Next, we call the Authorize() method, passing our username and password:

$Yt.Authorize(“username”, “password”)

And then finally, we upload the video using the Upload() method! Parameters are as follows:

  • Title
  • Description
  • Category (library has a spelling error: “catagory”)
  • Video keywords
  • Path to local video file
  • Error variable (pre-defined variable, used as an “out” parameter)

$result = $Yt.Upload(“Test upload”, “test upload”, [Trails.YouTube+Catagory]::Howto, “keyword1”, “C:\Users\trevor.sullivan\Videos\SMS_BootImagePackage.wmv”, [ref] $UploadError)

Optionally, you can print out any resulting error message:


The entire script put together looks like this:

$LibYt = [System.Reflection.Assembly]::LoadFile(“c:\Users\trevor.sullivan\Documents\\YouTube\bin\Release\YouTube.dll”)

$Yt = New-Object Trails.YouTube

$UploadError = “”

$Yt.Authorize($(Read-Host -Prompt “Please enter username”), $(Read-Host -Prompt “Please enter password”))

$result = $Yt.Upload(“Test upload”, “test upload”, [Trails.YouTube+Catagory]::Howto, “keyword1”, “C:\Users\trevor.sullivan\Videos\SMS_BootImagePackage.wmv”, [ref] $UploadError)


If we wanted to “variable-ize” (yes, I made that up just now) this script a bit more, we could do something like this:

### Define YouTube library path

$YtDll = “c:\Users\trevor.sullivan\Documents\\YouTube\bin\Release\YouTube.dll”

### Get YouTube username and password

$YtUser = $(Read-Host -Prompt “Please enter username”)

$YtPass = $(Read-Host -Prompt “Please enter password”)

######## Set up video variables ########

$vTitle = “Video Title”

$vDesc = “Video Description”

$vCat = [Trails.YouTube+Catagory]::Howto

$vKeyword = “keyword1”

$vPath = “C:\Users\trevor.sullivan\Videos\SMS_BootImagePackage.wmv”

$vErr = “”

### Load YouTube library, login, upload video, and echo error

$LibYt = [System.Reflection.Assembly]::LoadFile($YtDll)

$Yt = New-Object Trails.YouTube

$Yt.Authorize($YtUser, $YtPass)

$result = $Yt.Upload($vTitle, $vDesc, $vCat, $vKeyword, $vPath, [ref] $vErr)


Hope this post helps!


Posted in powershell, scripting, tools | Tagged: , , , , , , , , , , , , , | 5 Comments »

Handy Process Management Utilities

Posted by Trevor Sullivan on 2009/12/17

Hey guys,

I just wanted to share a couple of cool utilities to assist with managing processes.

ImageCFG (

ImageCFG is a utility that lets you tweak the CPU affinity of an executable. What does this mean? Well, if you have a multi-core system, or even a hyper-threaded (virtual multi-core) system, you can restrict which cores a process can execute on. This may be desirable if there is a program that tends to hog processor time, such as a video editing application, and you want to allow it to run, but only on a restricted set of resources.

Prio (

Although I haven’t used Prio yet, it looks to be a great, free (for personal use) program. A couple of features it offers are:

  • Save process priority settings – useful if there’s a program you always want to run in low priority / background mode
  • Always elevate certain processes – maintain good security with UAC, and convenience at the same time!
  • TCP / IP task manager tab – lets you view open network connections in real-time through task manager


I had a need for a piece of the Prio tool, but didn’t need the entire toolset (or be able to use it freely for commercial use), so I wrote a small C# utility called “SetProcessPriority” that does just what I need it to, instead. This utility searches for a process by it’s friendly name (eg. ‘notepad‘, but without the ‘.exe’) every 2 seconds, and sets the process’ priority to “Below Normal.” This was necessary to server as a work-around to a software bug I was having with the Microsoft ConfigMgr console.

Here is the C# code for it:

using System;
using System.Diagnostics;
using System.Threading;
using System.Windows.Forms;

namespace SetProcessPriority
    class Program
        static void Main(string[] args)
            if (args.Length != 1) return;

            string procname = args[0];
            while (1 == 1)
                    Process[] procs = Process.GetProcessesByName(procname);
                    foreach (Process proc in procs)
                        proc.PriorityClass = ProcessPriorityClass.BelowNormal;
                catch { }

You can compile the above code in Visual Studio 2008, targeting the .NET 2.0 framework. Once you’ve compiled it into a .NET assembly, you can simply write a batch script to call it, like this:

SetProcessPriority notepad


Anyway, I hope this post helps someone out, out there!

Merry Christmas to all!

Posted in scripting, tools | Tagged: , , , , , , | Leave a Comment »

PowerShell: Allowing all file exts. for ConfigMgr

Posted by Trevor Sullivan on 2009/12/15

When running Configuration Manager 2007 on Windows Server 2008, you must enable certain file extensions in IIS, so that a BITS-enabled distribution point (DP) can transfer files correctly. See this Technet article for more information.

If you would like to enable all of the file extensions on a BITS-enabled DP, simply run the following PowerShell code:

function EnableAllExtensions()
    $ahPath = "$((Get-WmiObject Win32_OperatingSystem).SystemDirectory)\inetsrv\config\applicationHost.config"
    if (-not [IO.File]::Exists($ahPath)) { return } # If file doesn't exist, return
    $xd = New-Object Xml.XmlDocument
    $nodes = $xd.SelectNodes("/configuration/system.webServer/security/requestFiltering/fileExtensions/add")
    foreach ($child in $nodes)
        $child.SetAttribute("allowed", "true")

. EnableAllExtensions

Hope this helps!

Don’t forget to make your WebDAV configuration edits using PowerShell also!

Posted in configmgr, powershell, scripting, tools | Tagged: , , , , , , , , | Leave a Comment »

PowerShell: Clean up AD Computer Accounts

Posted by Trevor Sullivan on 2009/09/19

Update (2009-11-03): I have posted a newer version of this script. Please visit this link for information.


I recently wrote a script to clean up workstation accounts in our Active Directory domain. It’s not perfect, but it was a good learning experience, as I found out there are some gritty details when working with Integer8 values. I plan on adding more features in the future, including logging results of the script to an Excel document, for easy readability. If you have any suggestions, please send them my way; I may or may not have time to implement them, but am always open to constructive feedback.

I would suggest stepping through the code using a tool like PowerGUI so you can understand exactly what is happening. I also take no responsibility for your use of this code (aka. use at your own risk). This script automatically disables and deletes computer accounts in Active Directory! It is not necessarily suitable for all Active Directory configurations, depending on how your GPOs are set up, and so on, so please ensure that you understand its purpose prior to executing it.

Note: This script leverages the Description attribute of your computer accounts in AD, so it can be overwritten!

The way the script works is basically this:

1. You define an OU where disabled computer accounts will be moved to ($DisabledDn)

2. Stage 1: Script attempts to evaluate when computer accounts in the “Disabled OU” were disabled (from the description attribute — see Stage 2)

3. If the parsed disable date is greater than 30 days, it will delete it, otherwise it will leave it alone

4. Stage 2: Script evaluates pwdlastset attribute of workstation accounts in the domain

5. If pwdlastset is greater than 60 days, it will: 1) disable the account, 2) write the current date to the description attribute, and 3) move it to the disabled OU

Note: The first time you execute the script, the Stage 1 area will pretty much do nothing, since no workstations have been populated into the Disabled OU yet. Once the script begins disabling accounts, they will then be evaluated in Stage 1 each time the script is run. It’s kind of  backwards way of thinking about it, so it may not make sense right off the bat, but don’t think about it too much.

Note 2: The Stage 1 and Stage 2 verbage I used above are not identified in the script anywhere. Stage 1 correlates to the DeleteDisabledAccounts function, and Stage 2 correlates to the DisableOldAccounts function.


# Author: Trevor Sullivan
# Lessons learned:
# 1. When using a DirectorySearcher object, property names are lower case
# 2. Must explicitly cast 64-bit integers from AD

${DisabledDn} = ‘ou=Disabled,ou=Workstations,dc=mycompany,dc=com’

function DisableOldAccounts(${TargetDn}, ${DisableAge} = 60)
${Computers} = GetComputerList ${TargetDn}

foreach (${Computer} in ${Computers})
# PwdLastSet is a 64-bit integer that indicates the number of 100-nanosecond intervals since 12:00 AM January 1st, 1601
# The FromFileTime method converts a 64-bit integer to datetime
$PwdLastSet = [DateTime]::FromFileTime([Int64]”$(${Computer}.Properties[‘pwdlastset’])”)
${CompAge} = ([DateTime]::Now – $PwdLastSet).Days
if (${CompAge} -gt ${DisableAge})
LogMessage “$($Computer.Properties[‘cn’]) age is ${CompAge}. Account will be disabled” 2
DisableAccount $Computer.Properties[‘distinguishedname’]
LogMessage “$($Computer.Properties[‘cn’]) age is ${CompAge}, $($Computer.Properties[‘pwdlastset’]), ${PwdLastSet}” 1

function GetComputerList($TargetDn)
${tFilter} = ‘(&(objectClass=computer)(|(operatingSystem=Windows 2000 Professional)(operatingSystem=Windows XP*)(operatingSystem=Windows 7*)(operatingSystem=Windows Vista*))’
${Searcher} = New-Object System.DirectoryServices.DirectorySearcher $tFilter
${Searcher}.SearchRoot = “LDAP://${TargetDn}”
${Searcher}.SearchScope = [System.DirectoryServices.SearchScope]::Subtree
# Page size default in Active Directory is 1000
${Searcher}.PageSize = 1000
$Results = $Searcher.FindAll()
LogMessage “Found $($Results.Count) computer accounts to evaluate for disablement” 1
return $Results

# Set description on computer account, disable it, and move it to the Disabled OU
function DisableAccount($dn)
# LogMessage “DisableAccount method called with param: ${dn}” 1
# Get a reference to the object at <distinguishedName>
$comp = [adsi]”LDAP://${dn}”
# Disable the account
# LogMessage “userAccountControl ($($comp.Name)) is: $($comp.userAccountControl)”
$comp.userAccountControl = $comp.userAccountControl.Value -bxor 2
# Write the current date to the description field
$comp.Description = “$(([DateTime]::Now).ToShortDateString())”

[Void] $comp.SetInfo()

# Parameter ($DeleteAge): Days from disable date to delete computer account
function DeleteDisabledAccounts($DeleteAge)
# Get reference to OU for disabled workstation accounts
${DisabledOu} = [adsi]”LDAP://${DisabledDn}”

${Searcher} = New-Object System.DirectoryServices.DirectorySearcher ‘(objectClass=computer)’
${Searcher}.SearchRoot = $DisabledOu
${Searcher}.SearchScope = [System.DirectoryServices.SearchScope]::Subtree
# Page size is used to return result count > default size limit on domain controllers. See:
${Searcher}.PageSize = 1000
LogMessage “Finding computers to evaluate for deletion in container: ${DisabledDn}” 1
${Computers} = ${Searcher}.FindAll()

foreach (${Computer} in ${Computers})
${DisableDate} = [DateTime]::Parse(${Computer}.Properties[‘description’])
trap {
LogMessage “Couldn’t parse date for $($Computer.Properties[‘cn’])” 3
${CurrentAge} = ([DateTime]::Now – ${DisableDate}).Days
if (${CurrentAge} -gt ${DeleteAge})
LogMessage “$(${Computer}.Properties[‘cn’]) age is ${CurrentAge} and will be deleted” 2
#   $DisabledOu.Delete(‘computer’, ‘CN=’ + ${Computer}.Properties[‘cn’])
LogMessage “$(${Computer}.Properties[‘cn’]) age is ${CurrentAge} and will not be deleted” 1

function LogMessage(${tMessage}, ${Severity})
1 {
$LogPrefix = “INFO”
$fgcolor = [ConsoleColor]::Blue
$bgcolor = [ConsoleColor]::White
2 {
$LogPrefix = “WARNING”
$fgcolor = [ConsoleColor]::Black
$bgcolor = [ConsoleColor]::Yellow
3 {
$LogPrefix = “ERROR”
$fgcolor = [ConsoleColor]::Yellow
$bgcolor = [ConsoleColor]::Red
default {
$LogPrefix = “DEFAULT”
$fgcolor = [ConsoleColor]::Black
$bgcolor = [ConsoleColor]::White

Add-Content -Path “AD-Workstation-Cleanup.log” -Value “$((Get-Date).ToString()) ${LogPrefix}: ${tMessage}”
Write-Host -ForegroundColor $fgcolor -BackgroundColor $bgcolor -Object “$((Get-Date).ToString()) ${LogPrefix}: ${tMessage}”

function Main()

LogMessage “Beginning workstation account cleanup script” 1

# Start by deleting accounts that are already disabled, if they are old enough
DeleteDisabledAccounts 30

# Disable accounts that are older than X days
DisableOldAccounts ([adsi]””).distinguishedName 60

LogMessage “Completed workstation account cleanup script” 1


Posted in Uncategorized | Tagged: , , , , , , , , | 13 Comments »