Active Directory OU picker in powershell

¡ This version is deprecated! New version available here.

I recently started to create GUI tools using my favorite Powershell tool: Sapien Powershell Studio 2012. The function of one of my first tools was to create users and security groups in a specific OU in Active Directory. I decided to create a seperate GUI form for the making of a OU picker tool, so I could re-use it in other projects. I found a great base for this tool in a blogpost by thepip3r’s. This was a great start but I wanted to give it a more native AD look & feel and I also wanted to be able to create a new OU if necessary.

The end result looks like this:

AD OU Picker     AD OU Picker New OU

As you can see the icons used for OU’s, folders and domains are the same as in AD Users and Computers. When selecting an OU you can create a new OU by clicking the New OU button. This will create a new OU branch in the treeview and let’s you type in a name immediately. As soon as the new name is confirmed by hitting enter the OU will be created.

One of the challenges in this project was that when you use the native powershell command for creating a new OU, New-ADOrganizationalUnit, you’ll receive an “Access Denied” error. For some reason you’re not allowed to create new AD objects with powershell when you’re logged on to a Domain Controller. Running the script as a domain admin and with elevated rights wasn’t enough to overcome this so I used the good old-fashioned DSADD command to create the OU. This will require your main script to be run with elevation. If not elevated hitting the New OU button will generate an error. You’ll still be able to select an OU though.

Hitting the OK button will create a script level variable $objSelectedOU which contains the object of the OU you selected.

If you want to use this for your own GUI’s you can view or download the code here. It’s a textfile but if you change the extension to ps1 and run it on a domain controller it will run on its own. You can copy the main function and use it in your own Powershell scripts/GUI’s. If you download it I’d appreciate a comment.

Edit: In order to make this script easier for you to use I’ve turned it into a function that will return the OU object automatically. It’s available for download here. Now you can copy the entire function Choose-ADOrganizationalUnit into your script and use it like this:

$OU = Choose-ADOrganizationalUnit

I think you’ll find it more user friendly (Yeah, I’m evolving).

MicaH

Edit #2: I’ve created an improved version of this function in a new blogpost: https://itmicah.wordpress.com/2016/03/29/active-directory-ou-picker-revisited/. If you’re currently using this older version please update. The new version is faster and more advanced.

MicaH

Advertisements

About MicaH

I'm a Technical Specialist at PepperByte BV (the Netherlands).
This entry was posted in Powershell, Sapien Powershell Studio and tagged , , , , , , , , , , , , , , , . Bookmark the permalink.

49 Responses to Active Directory OU picker in powershell

  1. Vinit says:

    I liked this code very much as it was the thing that i was looking for……but it takes a lot of time to load (form)…..

    • MicaH says:

      Really? It only takes about three seconds on my testservers. I guess it depends on the depth of your OU structure.

      • Vinit says:

        Here is the script that I have but it has couple of issues:
        1) It gives me complete path i.e. OU = ABC, DC = xyz, DC= COM…….However i want only ABC to be appeared.
        2) i want it that it should stop at sites name…….i mean should not go for looking for users under site name.

        Can you please reply on that ?

        # the Main function that can be loaded or gets started at the end of the script

        Function Browse-ActiveDirectory {

        $root=[ADSI]”

        # Try to connect to the Domain root

        [void]$Root.psbase.get_Name()}

        # Make the form
        # add a reference to the forms assembly
        [System.Reflection.Assembly]::LoadWithPartialName(“System.Windows.Forms”)

        $form = new-object Windows.Forms.form
        $form.Size = new-object System.Drawing.Size @(510,560)
        $form.text = “AD”
        $form.StartPosition = “CenterScreen”
        $form.Topmost = $True
        $form.Opacity = 2.0
        $form.ShowIcon = $False
        $form.AutoSize = $False
        $form.MinimizeBox = $False
        $form.MaximizeBox = $False
        $form.SizeGripStyle= “Hide”
        $form.WindowState = “Normal”
        $form.FormBorderStyle=”Fixed3D”

        # Make TreeView to hold the Domain Tree

        $TV = new-object windows.forms.TreeView
        $TV.Location = new-object System.Drawing.Size(10,10)
        $TV.size = new-object System.Drawing.Size(470,470)
        $TV.Anchor = “top, left, right”

        # Add the Button to close the form and return the selected DirectoryEntry

        $btnSelect = new-object System.Windows.Forms.Button
        $btnSelect.text = “&Select”
        $btnSelect.Location = new-object System.Drawing.Size(10,480)
        $btnSelect.size = new-object System.Drawing.Size(70,30)
        $btnSelect.Anchor = “Bottom, Left”
        $btnSelect.add_Click($handler_OKButton_Click)

        # If Select button pressed set return value to Selected DirectoryEntry and close form

        $handler_OKButton_Click=
        {
        $script = $TV.SelectedNode.text
        Write-Host $script
        $script:Return = $true
        $form.close()
        }

        # Add Cancel button

        $btnCancel = new-object System.Windows.Forms.Button
        $btnCancel.text = “&Cancel”
        $btnCancel.Location = new-object System.Drawing.Size(90,480)
        $btnCancel.size = new-object System.Drawing.Size(70,30)
        $btnCancel.Anchor = “Bottom, Left”

        # If cancel button is clicked set returnvalue to $False and close form
        $btnCancel.add_Click({$script:Return = $false ; $form.close()})

        # Create a TreeNode for the domain root found

        $TNRoot = new-object System.Windows.Forms.TreeNode(“Root”)
        #$TNRoot.Name = $root.name
        $TNRoot.Text = $root.distinguishedName
        $TNRoot.tag = “NotEnumerated”

        # First time a Node is Selected, enumerate the Children of the selected DirectoryEntry

        $TV.add_AfterSelect({

        if ($this.SelectedNode.tag -eq “NotEnumerated”) {

        $de = [ADSI]”LDAP://$($this.SelectedNode.text)”

        # Add all Children found as Sub Nodes to the selected TreeNode

        $de.psBase.get_Children() |
        foreach {
        $TN = new-object System.Windows.Forms.TreeNode
        $TN.Name = $_.name
        $TN.Text = $_.distinguishedName
        $TN.tag = “NotEnumerated”
        $this.SelectedNode.Nodes.Add($TN)

        }

        # Set tag to show this node is already enumerated

        $this.SelectedNode.tag = “Enumerated”

        }

        })

        # Add the RootNode to the Treeview

        [void]$TV.Nodes.Add($TNRoot)

        # Add the Controls to the Form

        $form.Controls.Add($TV)
        $form.Controls.Add($btnSelect)
        $form.Controls.Add($btnCancel)

        # Set the Select Button as the Default

        $form.AcceptButton = $btnSelect

        $Form.Add_Shown({$form.Activate()})
        [void]$form.showdialog()

        # Return selected DirectoryEntry or $false as Cancel Button is Used
        Return $script:Return
        }

        # If used as a script start the function

        Set-PSDebug -Strict:$false # Otherwise Checking the Switch parmeter does fail (Bug ?)

        if ($LoadOnly.IsPresent) {

        # Only load the Function for interactive use

        if (-not $MyInvocation.line.StartsWith(‘. ‘)) {
        Write-Warning “LoadOnly Switch is given but you also need to ‘dotsource’ the script to load the function in the global scope”
        Write-Host “To Start a script in the global scope (dotsource) put a dot and a space in front of path to the script”
        Write-Host “If the script is in the current directory this would look like this :”
        Write-Host “. .\ActiveDirectoryBrowser.Ps1”
        Write-Host “then :”
        }
        Write-Host “The Browse-ActiveDirectory Function is loaded and can be used like this :”
        Write-Host ‘$de = Browse-ActiveDirectory’
        Set-alias bad Browse-ActiveDirectory
        }
        Else {

        #start Function
        . Browse-ActiveDirectory $root

        }

      • MicaH says:

        1) change $TNRoot.Text = $root.distinguishedName to $TNRoot.Text = $root.name
        2) I’d have to look into that in a test environment. I’ll get back to you if I get a chance to look at it.

      • Vinit says:

        Thanks…..it changes root from OU = ABC, DC = xyz, DC= COM to be appeared as ABC but not children()…..However i tried to change $TN.text = $_.distinguishedname to $TN.text = $_.name but that doesn’t work and throws error.
        Any help would be appreciated.

  2. Cory says:

    Hello,

    Thanks for the code, it is just what I was looking for. I made a small modification to enable choosing a different child domain. It may be beneficial to others, here is the change:

    $FormEvent_Load={
    Import-Module ActiveDirectory
    $objIPProperties = [System.Net.NetworkInformation.IPGlobalProperties]::GetIPGlobalProperties()
    $strDNSDomain = $objIPProperties.DomainName.toLower()
    $domains=(get-addomain $strDNSDomain).childdomains
    $domains += $strDNSDomain
    Write-Host “Please pick a domain.”
    $i = 0
    foreach ($domain in $domains){
    write-host $i -> $domain
    $i++
    }
    $d = Read-Host “Domain: ”
    #may not work if logged on to a child domain — need to test

    $strDNSDomain = $domains[$d]

    $strDomainDN = $strDNSDomain.toString().split(‘.’); foreach ($strVal in $strDomainDN) {$strTemp += “dc=$strVal,”}; $strDomainDN = $strTemp.TrimEnd(“,”).toLower()
    Build-TreeView
    }

    Also line 99 would need to change to:
    $OUs = Get-ADObject -Filter ‘ObjectClass -eq “organizationalUnit” -or ObjectClass -eq “container”‘ -SearchScope OneLevel -SearchBase $dn -Server $strDNSDomain

    Thanks for the help!

    • MicaH says:

      You’re welcome, Cory. Great idea about the child domains, I hadn’t thought of that. Maybe I’ll integrate your solution in my script. Thanks for sharing it and for your positive feedback.

  3. Jay says:

    Michaja,
    I also have PowerShell Studio 2012. Would it be possible to provide the .pff file of your script? This would help me to add more controls for specific applications related to AD.

    -Jay

    • MicaH says:

      Here you go: https://dl.dropboxusercontent.com/u/62204506/Blog/AD-OU-Selection.zip
      Just know that if you develop a kick-ass tool and get paid big for it I will be expecting royalties! ;^)

      • Jay says:

        Thank you very much, and it’s a deal. Currently trying to use your form to pick OU’s, and eventually users to copy them from a production domain into a test domain. Each side has a text box to enter in the root of the domain, Then you select the OU you want to copy (eventually would like to select multiples) then create that OU in the new domain based off where you select the starting OU. During the creation it would replace the original DC=blah,CN=blah with the new DC, and CN’s from the root domain entered into the text box.

        Same thing with the users. It would copy the users with a set number of attributes replacing anything like their UPN or email with the new domain suffix.
        Currently client is doing this manually for three different test domains, trying to simplify the process without using scripts that require a csv file to be edited with find and replace.

        Should be fun, and don’t be surprised if I hit you back with questions lol.

        Thanks again MicaH,

        -Jay

      • MicaH says:

        You’re welcome, Jay. Us scripting geeks need to stick together, right?

  4. Dave says:

    This is awesome. I’m wanting to use this for our techs when they image comptuers via SCCM to be able to choose the OU the PC ends up in. I’m pretty new to PS but love it. Where would the output be for the OU they choose so I can pipe it into the join domain script?

    Thanks!!!!!

    • MicaH says:

      That’s an interesting use for it! I hadn’t considered that. I’ve given this some thought but sadly my script can’t be incorporated into a PE environment. It only runs on a domain joined computer and the account you run it with must have at least read rights on your AD structure. So you’d have to connect to a Domain Joined server first and the run the script through that connection. However, even thought PowerShell and DotNet can both run within WinPE you can’t use PowerShell remoting in a PE environment. Maybe you could try psexec to run the script remotely but that’s a long shot! If you do get this working I’d like to hear all about it. It would be a great addition to the SCCM menu structure!

      Anyway, back to answer you’re original question! The script doesn’t generate output, it just sets a variable for your scripting environment containing the OU object you selected. But it could easily be customized to set the appropriate TaskSequence variable for SCCM. Just find the line in the script that looks like this:

      $buttonOK_Click={

      }

      As you’ve guessed this is where you can customize what happens with the selected OU information. Hope it helps and thanks for your feedback!

      • Dave says:

        Hmm… Will work on that, but if not for imaging would be useful for mass running up PCs from the factory that have already been imaged with our image as they would be domain members when they come up. Already have a script in the run once that prompts to change the computer name when they come up. I hadn’t actually picked in OU while running the script until this morning. When I do I get the following error:

        Get-ADObject : Cannot validate argument on parameter ‘Identity’. The argument is null. Supply a
        non-null argument and try the command again.
        At line:178 char:50
        + $script:objSelectedOU = Get-ADObject -Identity $SelectedOU.name
        + ~~~~~~~~~~~~~~~~
        + CategoryInfo : InvalidData: (:) [Get-ADObject], ParameterBindingValidationException
        + FullyQualifiedErrorId : ParameterArgumentValidationError,Microsoft.ActiveDirectory.Management.C
        ommands.GetADObject

      • MicaH says:

        Hi Dave. I’ve discovered this happens on OS’s higher than 2008 R2. I’ve corrected the issue. Please re-download the script and try again. If you have anymore issues please let me know. If it works now I’d like to know also.

        Thanks for the feedback!

        MicaH

  5. vthinktank says:

    Has anyone tried this using XAML and Powershell ?

  6. jeffirvine says:

    Hi MicaH, I just wanted to say thank you for posting all of this. I have been working on an “AD Updater” for our front desk staff and needed exactly this. I originally took your script and modified it to work for my project to only select Search OUs. I spent a lot of time on it yesterday and finally got it to work but kept thinking to myself, “I wish I could see his project file so I could see exactly how he did all this”. If only I would have scrolled down into the comments to see that you posted it! Doh! It’s all good though, because I learned some things and that’s what it’s really all about, right?

    Thanks again for this.

  7. Hi MicaH, this is really a great GUI! Thank you very much for the hard work here.
    – Is it possible to have checkboxes in front of the OU? So you can select multiple at the same time?
    – It takes 25 seconds to load, it would be great if it’s possible to preload it and then just call it when I need it. Still need to think about how to implement this properly.
    Thank you for your feedback.

    • MicaH says:

      Hi Brecht. Checkboxes shouldn’t be a problem, although checked boxes are not treated as if they are “selected”. For a good tutorial check this link. You might already be able to select multiple OU’s bij holding the Ctrl key but I haven’t tried that myself, so dont hold me to it.
      Preloading sounds interesting. You could alter the function so that the form comes up invisible and call the function at the start of the script as a background job. You could then create a button that makes the form visible. You’ll have to build in a check wether the background job has finished or not. If not, you could change the mouse icon to ‘Waiting’ until it is ready, then change it back to normal. Otherwise, people will think the button isn’t working and they’ll just keep clicking it! This is all theoretical, I have no idea if it’ll work this way, but I thought I’d give you my two cents anyway. Hopefully it will point you in the right direction. Let me know how it turns out!!

      • Thank you MichaH, but I’m not using Sapien. In the meantime I managed to speed up your script a little bit. If you replace ‘Get-ADObject -Filter ‘ObjectClass -eq “organizationalUnit” -or ObjectClass -eq “container”‘ -SearchScope OneLevel -SearchBase $dn’ with ‘Get-ADOrganizationalUnit -Filter ‘Name -like “*”‘ -SearchBase $dn -SearchScope OneLevel’, it’s 50% faster.

      • MicaH says:

        Nicely spotted! Thanks for the feedback, I’ll incorporate your solution in my script.

      • Hi MicaH, I’ve finished my project with some small improvements:
        – Multiple OU selection with checkboxes
        – Cancel button (didn’t need ‘New OU’)
        – Checkbox logic like it is in Windows forms
        Let me know what you think: http://brechtgijbels.blogspot.co.uk/2015/01/powershell-net-ad-ou-picker.html

      • MicaH says:

        Looks good! It works like it’s supposed to. By the way, I’ve reconcidered using your Get-ADOrganizationalUnit solution because you’ll only get the OU’s that way and I want the folders displayed as well.

  8. vthinktank says:

    can we please have the ps1 as file that could be downloaded.if that is acceptable agreeable possible

  9. Will Henderson says:

    Hi MicaH,

    Great script! I’m wondering about limiting the scope of the OUs that can be picked. For my purposes, I don’t need it showing the entire domain, just a couple of OUs in a specific part of the tree. I’ve tried a few things but no success yet. Any ideas?

    Will

    • MicaH says:

      That’s a tough one. Especially if the OU’s ate on different levels. It’s definitely possible but it’s gonna require some serious programing skills. I’d start by using the Get-ADOrganizationUnit commandlet to get the distinguished names of those OU’S and use these as a filter for the underlying OU’S.

    • Michael Robinson says:

      You can narrow the scope down to a subset by changing the field $strDomainDN to OU=TopOU,DC=a,DC=b,DC=c

  10. Wei-Yen Tan says:

    Hi Micah,

    Excellent script. I have an idea on how to use this. You mention in a previous comment that I could pass the variable to somewhere else.

    What I would like to do is to put the contents of the variable into a form of a distinguished name and place in a text field on a form.

    I want to use this script to pick an OU that they can move a list of users to. CAn I do something like that?

    • MicaH says:

      That’s exactly what I wrote it for. The $objSelectedOU variable is in the script scope so you should be able to do something like:
      $Textbox.text = $objSelectedOU.DistinghuisedName
      I’ve also just added a easier to use version: check out the Edit section at the bottom of the post. With the new version you can copy the main function into your script and do something like this:

      $OU = Choose-ADOrganizationalUnit
      $Textbox.text = $OU.DistinghuisedName

      • Wei-Yen Tan says:

        Thanks for this.

        I am using Powershell Studio like yourself and am very new to it.

        So I have a form….does that mean I copy the whole text that you mentioned into the beginning of the form?

        Thank you

      • MicaH says:

        Yes, you can insert the function anywhere you want and call the function with a button click action. It’s a bit hard to explain using text only but this is basically what you’d have to do:
        * create a form (or use existing) and place a textbox in it with a button next to it.
        * Dubbel-click the button. This will automatically create a button click action for you.
        * In this button click action call the imported function and save the result in a variable, like I showed you in the previous comment: $OU = Get-ADOrganizationUnit
        * Assuming your textbox is $textbox the next line in the button click action should be: $textbox.text = $OU.DistinguishedName
        * Run the script to see if it works: Click the button and my OU picker should emerge. When selecting an OU and clicking OK the distinguished name of the OU should now be in the textbox.

        Hope it helps clear things up.

        MicaH

  11. MicaH says:

    I’m on vacation now but if you’re still having difficulty let me know, I’ll cook up an example for you.

  12. Arne says:

    Hey MicaH,

    great script. I modified it a little so that it shows me only OUs and I’m also excluding some OUs that I don’t need to see.

    Now I’m just stuck with returning the selected OU to my .ps1 script.
    Im calling the OUPicker via: Start-Process -wait “$ScriptDirectory\OUtree.exe”

    In the OUtree.exe I have modified the OnApplicationExit function and $buttonOK_Click to the following:

    function OnApplicationExit {
    #Note: This function is not called in Projects
    #Note: This function runs after the form is closed
    #TODO: Add custom code to clean up and unload snapins when the application exits

    $script:ExitCode = $objSelectedOU #Set the exit code for the Packager
    }
    $buttonOK_Click = {
    #$selOU = $treeView1.SelectedNode.Name
    $global:objSelectedOU = $treeView1.SelectedNode.Name
    $textbox1.Text = $global:objSelectedOU
    $formChooseOU.close()
    }

    But I can’t get any value back to the original .ps1 script. Nothing of the following seem to work:

    Write-Host “LastExitCode: $LastExitCode”
    $LastExitCode
    Write-Host “objSelectedOU: $objSelectedOU”
    $objSelectedOU

    What am I missing here?
    Thanks in advance

  13. toothandnail says:

    Thanks! With this I’m going to make OSDed PCs go somewhere besides the default.

  14. mmoore5553 says:

    Micah,

    Can you make a small example. I am trying to figure out how to incorporate this. I am just using powershell thought and thought about using HTA or visual studio. If i could do this totally in powershell that would be great too. I am just stuck …

    • MicaH says:

      OK. I’ll cook something up for you. Are you using Sapien PowerShell Studio?

      • MicaH says:

        I’ve created a simple form in PowerShell Studio for you. You can download the form file here: OUPicker_example.pff. If you’re not using Sapien Powershell Studio I can post the PowerShell code it generates but that will be harder to read.

      • mmoore5553 says:

        Thank you. I will test it out. I do not have sapien but will try the trial an if good will buy it. Can you make this a program like visual studio and be an exe ? I am not sure if sapien will work like that or not.

      • MicaH says:

        Sapien will let you save your forms/projects as .exe files. The new version can even create an .msi for you if you have dependencies you’d like to include. This won’t work in the trial version though. I think it can be done in visual studio as well but I have no experience with that.

  15. supercheetah says:

    Firstly, if this is a double post, I apologize. I thought I posted it, but can’t find it.

    I really appreciate this code, but it was really, really slow on our AD forest because it’s pretty deeply nested. Also, I had the issue that we have multiple domains in our AD forest, so I had to add the capability for it to query for those different domains

    I tried to refactor it, but had a hard time doing so, Instead, I just started from scratch using your code as just inspiration.

    It looks at the root domain to see what domains are all available, and creates a hash of the hostnames of the DCs. It assumes all OUs have child nodes in them, but only actually finds out from the DC when the user tries to expand them.

    Here’s the results of my work: https://gist.github.com/supercheetah/b68023f3254dfc9a6497

  16. Hi Micah,
    Thanks for sharing this script! I used it in my own Powershell GUI tool. You can see the result on https://www.serverknowledge.net/powershell/ad-user-info-exporter/.

  17. Stephen says:

    Love the script.

    Thank you for sharing.

  18. Pingback: Active Directory OU picker revisited | MicaH's IT blog

  19. MicaH says:

    Everyone, I’ve created a better version of this function in a new blog post: https://itmicah.wordpress.com/2016/03/29/active-directory-ou-picker-revisited/. If you’re using the old version please update. The new one is way better and faster, thanks to some of your idea’s in this comment section. Let me know what you think!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s