Building a responsive TreeView that displays your AD Domain

Often times you might find yourself wanting to build a TreeView in your form that represents your AD domain structure. The following post will show you how to do that. (Note that I pieced together this solution with the assistance of many Google searches and other folks code samples, however this was done over time and I don’t have any sources available.)

First you’ll want to setup your form with with a TreeView object. You’ll then need to devise a way to initially populate the TreeView. I personally have a function for initially building the TreeView which you can then call with a button click, or the form loading. (In addition to having the TreeView built when the form loads, you’ll also need to create a script scoped variable called “DNsLoaded” that’s an array object. You’ll see why later on.) The function that builds the TreeView contains the following:

treeview-addomain

if ($treeNodes)
{
	$treeview.Nodes.remove($treeNodes)
}
$script:treeNodes = New-Object System.Windows.Forms.TreeNode
$Script:ADDomain = Get-ADDomain
$script:treeNodes.text = $ADDomain.DNSRoot
$script:treeNodes.Name = $ADDomain.DNSRoot
$script:treeNodes.Tag = $ADDomain.DistinguishedName
$script:treeNodes.StateImageIndex = 0
$script:treeNodes.SelectedImageIndex = 1
$treeView.Nodes.Add($script:treeNodes) | Out-Null
Get-NextLevel -selectedNode $treeNodes -dn $ADDomain.DistinguishedName
$treeNodes.Expand()

For a bit of explaining of what’s going on…

  • Lines 1-4 we looks to see if the $treeNodes variable already exists. If it does, it’s assumed the TreeView object is already populated and needs cleared.
  • Line 5 we create a base TreeNode object.
  • Line 6 we run the Get-ADDomain cmdlet.
  • Line 7-9 we populate the Text, Name, and Tag values of the base TreeNode object. The Text and Name values are populated with the DNS root (domain.local), while the Tag value is populated with the DN (DC=domain,DC=local).
  • Line 10 and 11 are only necessary if you have images that you want to use within the TreeView. The images a placed next to the node, with each node in this example being a container or OU.
  • Line 12 we add the TreeNode object to the TreeView.
  • Line 13 we run a function that we’ll cover next.
  • Line 14 causes the base node to expand in the TreeView.

The next function you’ll want to create is the one referenced above in line 13. This function is called anytime you want to load the next layer of objects in the TreeView.

treeview-ad-domain-get-nextlevel

function Get-NextLevel
{
	param (
		$selectedNode,
		$dn
	)
	$form.Cursor = 'WaitCursor'
	ForEach ($OU in (Get-ADObject -Filter 'ObjectClass -eq "organizationalUnit" -or ObjectClass -eq "container"' -SearchScope OneLevel -SearchBase $dn))
	{
		Add-Node -selectedNode $selectedNode -dn $OU.DistinguishedName -name $OU.Name -preload $true
	}
	$form.Cursor = 'Default'
}

To explain the Get-NextLevel function…

  • Lines 3-6 we collect two parameters, the node object that we’ll be adding new nodes to, and the distinguished name of the OU that’s currently selected.
  • Line 7 sets the the cursor to the busy cursor while the function processes.
  • Line 8 is where we run Get-ADObject, specifically searching for OU’s and containers, with the search scope set to one level (so that we don’t get objects more than one level deep) and the search base set to the distinguished name that was passed as a parameter to the function. For each object returned, we run line 10.
  • Line 10 we run the function Add-Node. This function will be covered next, however it simply adds a new node object to the node object received as a parameter. In addition, we pass the parameter ‘preload’ as true. This will be covered next.

The last function necessary to create is Add-Node.

treeview-ad-domain-add-node

function Add-Node
{
	param (
		$selectedNode,
		$dn,
		$name,
		$preload
	)
	If (!($DNsLoaded -contains $dn))
	{
		$newNode = new-object System.Windows.Forms.TreeNode
		$newNode.Name = $dn
		$newNode.Text = $name
		$newNode.StateImageIndex = 0
		$newNode.SelectedImageIndex = 1
		If ($preload -eq $true)
		{
			ForEach ($OU in (Get-ADObject -Filter 'ObjectClass -eq "organizationalUnit" -or ObjectClass -eq "container"' -SearchScope OneLevel -SearchBase $dn))
			{
				Add-Node -selectedNode $newNode -dn $OU.DistinguishedName -name $OU.Name -preload $false
			}
		}
		Else
		{
			$Script:DNsLoaded += $dn
		}
		$selectedNode.Nodes.Add($newNode) | Out-Null
		return $newNode
	}
}

Now to explain the Add-Node function…

  • Lines 3-8 we collect 4 parameters. SelectedNode (the node in the treeview that we’re adding a node to), DN (the Distinguished Name of the OU we’re creating a node for), Name (the Name of the OU we’re creating a node for), and PreLoad (More details shortly…).
  • Line 9 we check to see if the DNsLoaded array contains the DN passed to the function. More details on why this is done later, however if the DN is not present in the array, lines 11 through 28 are processed.
  • Lines 11-15 we create a new node object and set the name (to the DN), text (to the name), and image states.
  • Lines 16-26 is where the PreLoad parameter comes into play. The PreLoad parameter is used to populate the new node being create with the next level of OU’s. This is done so that in the TreeView you’ll see a plus indicator next to the new node that was created in line 11 if there are sub OU’s to the OU passed on line 5. One line 20 you’ll see that the Add-Node function is called again, except this time with PreLoad set to false. This is so that we don’t try to load all the OU’s in our domain all at once. This is what makes this a responsive solution to populating the TreeView with a domain structure.
  • Line 27 we add the new node to the selected node that was passed to the Add-Node function.
  • Line 28 we return the NewNode object, however this isn’t necessary.

The last thing that needs done before this will work properly is to create a BeforeExpand event for the TreeView. In the BeforeExpand event, put the following:

treeview-ad-domain-before-expand

$treeview.SelectedNode = $_.Node
foreach ($node in $treeview.SelectedNode.Nodes)
{
	Get-NextLevel -selectedNode $node -dn $node.Name
}
  • Line 1 we’re ensuring that the node being expanded gets set as the SelectedNode. Then, because we’ve already pre-populated the sub-nodes (The OU’s within the OU we’re selecting.), we do a ForEach on each sub-node, and run the Get-NextLevel function.

Throw this all together and you’ll have yourself a very responsive TreeView of your AD Domain.

 

Providing a filter for your data results in a PowerShell form

I’ve often found myself wanting to provide filtering functionality in my forms for datagridviews that contain a lot of results. The following is what I’ve done to provide filtering functionality.

  1. Have a datagridview that contains one or more columns that you want to be able to filter text in.
  2. Create a timer in your form with an interval of around 500ms.  Create a ‘Tick’ event for the timer with the following:

    filter-timer_tick

    $timer.Stop()
    $datagridview.CurrentCell = $null
    If ($textbox.Text.Length -eq 0)
    {
    	foreach ($row in ($datagridview.Rows))
    	{
    		$datagridview.Rows[$row.Index].Visible = $true
    	}
    }
    Else
    {
    	foreach ($row in ($datagridview.Rows))
    	{
    		If ($row.Cells[$columnIndex/Name].Value -like "$($textbox.Text)*")
    		{
    			$datagridview.Rows[$row.Index].Visible = $true
    		}
    		Else
    		{
    			$datagridview.Rows[$row.Index].Visible = $false
    		}
    	}
    }
  3. Create a textbox within the form, then add a ‘TextChanged’ event with the following:

    filter-textbox_textchanged

    $timer.Stop()
    $timer.Start()

With the above in place if someone starts typing into the textbox and pauses for 500ms, the timer will execute and filter the datagridview for text that’s in the textbox. In the above example I use ‘-like’ when evaluating the cell content so that the use of a wildcard character is controlled by the textbox.

In the event that your datagridview (or data source in general) contains a ton of records, you could improve the experience by moving the actual filtering process to a Job, and then changing the cursor of the form to ‘WaitCursor’ until the job finishes.

Manage SSL/TLS settings in IE

The other day I had the need to configure SSL/TLS settings in IE for users in my organization. While I’m aware of the easy methods of managing IE settings using group policy, I always prefer to use Group Policy Preferences to manage the settings instead. After a bit of digging I identified that to manage the SSL/TLS settings you simply had to change the value of ‘SecureProtocols’ at HKCUSoftwareMicrosoftWindowsCurrentVersionInternet Settings.

Below are the possible values that you can use and what they do.

0 – Disable SSLTLS
8 – SSL 2.0
32 – SSL 3.0
40 – SSL 2.0, SSL 3.0
128 – TLS 1.0
136 – SSL 2.0, TLS 1.0
160 – SSL 3.0, TLS 1.0
168 – SSL 2.0, SSL 3.0, TLS 1.0
512 – TLS 1.1
520 – SSL 2.0, TLS 1.1
544 – SSL 3.0, TLS 1.1
552 – SSL 2.0, SSL 3.0, TLS 1.1
640 – TLS 1.0, TLS 1.1
648 – SSL 2.0, TLS 1.0, TLS 1.1
672 – SSL 3.0, TLS 1.0, TLS 1.1
680 – SSL 2.0, SSL 3.0, TLS 1.0, TLS 1.1
2048 – TLS 1.2
2056 – SSL 2.0, TLS 1.2
2080 – SSL 3.0, TLS 1.2
2088 – SSL 2.0, SSL 3.0, TLS 1.2
2176 – TLS 1.0, TLS 1.2
2184 – SSL 2.0, TLS 1.0, TLS 1.2
2208 – SSL 3.0, TLS 1.0, TLS 1.2
2216 – SSL 2.0, SSL 3.0, TLS 1.0, TLS 1.2
2560 – TLS 1.1, TLS 1.2
2568 – SSL 2.0, TLS 1.1, TLS 1.2
2592 – SSL 3.0, TLS 1.1, TLS 1.2
2600 – SSL 2.0, SSL 3.0, TLS 1.1, TLS 1.2
2688 – TLS 1.0, TLS 1.1, TLS 1.2
2696 – SSL 2.0, TLS 1.0, TLS 1.1, TLS 1.2
2720 – SSL 3.0, TLS 1.0, TLS 1.1, TLS 1.2
2728 – SSL 2.0, SSL 3.0, TLS 1.0, TLS 1.1, TLS 1.2

Values were obtained from: http://blogs.technet.com/b/iede/archive/2010/09/27/as-59-63-ssl-2-0-ssl-3-0-tls-1-0-tls-1-1-tls-1-2-verwenden.aspx

Working with Outlook using PowerShell

There was recently a request by an end user to have email items in a particular folder automatically deleted after they were older than X number of days. Right off I knew this could be done with the Outlook.Application com object, however I didn’t know exactly how. After a bit of digging and messing around I found that it could be done, and fairly easily at that.

The first step is establishing a variable and connecting it to the Outlook.Application com object like so.

$outlook = new-object -comobject “Outlook.Application”

You then need to establish a variable for the mapi namespace.

$mapi = $outlook.getnamespace(“mapi”)

At this point you’re essentially connected to the mailbox for Outlook. You can navigate through the objects in the mailbox by using GetDefaultFolder(), putting the appropriate number in the parenthesis. I’ve mapped out the default objects, listed below. Depending on your situation you might have more.

3 – Deleted Items
4 – Outbox5 – Sent Items
6 – Inbox
9 – Calendar
10 – Contacts
11 – Journal
12 – Notes
13 – Tasks
16 – Drafts
19 – Conflicts
20 – Sync Issues
21 – Local Failures
23 – Junk E-Mail
25 – RSS Feeds
28 – To-Do List
30 – Suggested Contacts

To connect to the Inbox folder, you would do:

$inbox = $mapi.GetDefaultFolder(6)

Now, connecting to a sub-folder doesn’t appear to be as straightforward as I thought it would be. So far the only way I’ve been able to connect to a sub-folder is by doing the following.

$subfolder = $inbox.Folders | Where-Object {$_.Name -eq “SubFolderName”}

You can then work with the sub-folder using the $subfolder variable. If you want to list all the items in the sub-folder, you can do:

$subfolder.items

If you’re only interested in items older than 1 day, you can do:

$date = (Get-Date).AddDays(-1)
$subfolder.items | Where-Object {$_.SentOn -lt $date}

Back to what I was trying to figure out, which is delete items in a folder that are X numbers of days old, you can delete items like so:

$date = (Get-Date).AddDays(-1)
$subfolder.items | Where-Object {$_.SentOn -lt $date} | ForEach-Object {$_.Delete()}

As I find more things worth mentioning that you can do with the Outlook com object I’ll be sure to add them.

Working with ACL’s in PowerShell

I recently had the need to make some permission changes to a folder and decided to take the time to figure out how to do it with PowerShell without using Cacls. The sources I used will be at the bottom of this post.

The first thing you need is a folder. For this we’ll use a folder called “Test” on C:, so “C:Test”. You then need to use Get-Acl to aquire the security descriptor for the folder.

$acl = Get-Acl “C:Test”

At this point you can call the $acl variable and you’ll get back a table that specifies the Path, Owner, and Access for the folder “Test”. To get a more friendly view of the Access column, call $acl.Access. You’ll then get a nice breakdown of each rule.

Now that we have information on the folder we can start making changes. The first thing I’ll cover is working with configuring inheritable permissions. For this we’ll use the ‘ObjectSecurity.SetAccessRuleProtection’ method. There are two parameters to this method, ‘isProtected’ and ‘preserveInheritance’. ‘isProtected’ is what determines if a folder inherits permissions. A value of true will break inheritance, while a value of false will allow inheritance. ‘preserveInheritance’ is only used if ‘isProtected’ equals true. When ‘isProtected’ equals false, the parameter ‘preserveInheritance’ still needs to be set, however it will be ignored. If you’re breaking inheritance on the folder, this parameter determines if the existing permissions are retained or removed. A value of true will preserve the existing access rules, while a value of false will remove existing access rules. An example of using this method to allow inheritance is below.

$acl.SetAccessRuleProtection($false,$false)

Here’s an example of using the method to remove inheritance but copy existing access rules.

$acl.SetAccessRuleProtection($true,$true)

And here’s an example of using the method to remove inheritance and also remove existing access rules.

$acl.SetAccessRuleProtection($true,$false)

Now onto configuring access rules. Let’s assuming that for this folder we’ve gone with leaving inheritance in place. By default we have the following existing access rules.

FileSystemRights  : ReadAndExecute, Synchronize
AccessControlType : Allow
IdentityReference : BUILTINUsers
IsInherited       : False
InheritanceFlags  : ContainerInherit, ObjectInherit
PropagationFlags  : None

FileSystemRights  : FullControl
AccessControlType : Allow
IdentityReference : NT AUTHORITYSYSTEM
IsInherited       : True
InheritanceFlags  : ContainerInherit, ObjectInherit
PropagationFlags  : None

FileSystemRights  : FullControl
AccessControlType : Allow
IdentityReference : BUILTINAdministrators
IsInherited       : True
InheritanceFlags  : ContainerInherit, ObjectInherit
PropagationFlags  : None

FileSystemRights  : ReadAndExecute, Synchronize
AccessControlType : Allow
IdentityReference : BUILTINUsers
IsInherited       : True
InheritanceFlags  : ContainerInherit, ObjectInherit
PropagationFlags  : None

Lets say we want to change the access rules so that the built in Users group has modify access to the folder. The first thing we need to do is create a variable that will store the new access rule. For this we’re going to use the ‘FileSystemAccessRule’ class. This class has 7 properties, however we’re only going to need to use 5 to give the Users group modify access. The properties we’ll use are:

  • IdentityReference: The identity that the access rule will apply to. In this case, it will be “Users”.
  • FileSystemRights: The access being given. In this case it will be “Modify”.
  • InheritanceFlags: Determines how this rule is inherited by child objects (folders and files). In this case it will be “ContainerInherit, ObjectInherit” (Container meaning folder, Object meaning file).
  • PropagationFlags: Determines how inheritance of this rule is propagated to child objects. In this case it will be “None”.
  • AccessControlType: Specifies whether a access rule is used to allow or deny access. In this case it will be “Allow”.

Putting this all together you’ll get this:

$rule = New-Object System.Security.AccessControl.FileSystemAccessRule(“Users”,”Modify”,”ContainerInherit, ObjectInherit”,”None”,”Allow”)

You then need to add the rule to the $acl variable we already created by doing this:

$acl.AddAccessRule($rule)

That’s all there is to creating new access rules and applying them to the $acl variable, however that’s not all you have to do to apply the access rule and the inheritance settings to the folder. I”ll get to the step that applies the changes right after we go over making changes to the ownership.

When working with ownership you have to use the ‘ObjectSecurity.SetOwner’ method.  To set the owner to the local Administrators group, we would run:

$acl.SetOwner([System.Security.Principal.NTAccount] “Administrators”)

In the end, once you’ve finished with making your modifications, you need to apply your changes to the folder. To do this we use the ‘Set-Acl’ cmdlet like so:

Set-Acl “C:Test” $acl

After you do that, you can relist your access rules and you’ll see that the Users group now has modify access in addition to the existing inherited rules. Just to point it out, you’ll notice that the access rule for granting the modify access will have the ‘IsInherited’ flag set to False.

FileSystemRights  : Modify, Synchronize
AccessControlType : Allow
IdentityReference : BUILTINUsers
IsInherited       : False
InheritanceFlags  : ContainerInherit, ObjectInherit
PropagationFlags  : None

FileSystemRights  : FullControl
AccessControlType : Allow
IdentityReference : NT AUTHORITYSYSTEM
IsInherited       : True
InheritanceFlags  : ContainerInherit, ObjectInherit
PropagationFlags  : None

FileSystemRights  : FullControl
AccessControlType : Allow
IdentityReference : BUILTINAdministrators
IsInherited       : True
InheritanceFlags  : ContainerInherit, ObjectInherit
PropagationFlags  : None

FileSystemRights  : ReadAndExecute, Synchronize
AccessControlType : Allow
IdentityReference : BUILTINUsers
IsInherited       : True
InheritanceFlags  : ContainerInherit, ObjectInherit
PropagationFlags  : None

And that’s all there is to it.

My sources for figuring this all out are below:

Blog post from Jose Barreto on TechNet that pointed me in the right direction (while this article does describe how to do the above as well, I feel my post is much more clear and to the point of how to do this)

MSDN article on the FileSystemAccessRule class

MSDN article on the SetAccessRuleProtection Method

Working with data tables in PowerShell

I’m creating this post for my own reference for the most part, however hopefully it will assist someone else as well.

The following can be ran in PowerShell to create a basic data table:

$Table = New-Object System.Data.DataTable

For each column you want in the table, you’ll want to run something like the following:

$column = New-Object System.Data.DataColumn ‘NameofColumn’,([COLUMNTYPE])
$Table.Columns.Add($column)

Or to simplify things:

$Table.Columns.Add((New-Object System.Data.DataColumn ‘NameofColumn’, ([COLUMNTYPE])))

In the above, ‘NameofColumn’ is whatever you want the column title to be. It can contain spaces and symbols. COLUMNTYPE should be replaced with the type of column you want it to be. I most commonly use ‘string’ and ‘datetime’. You can reference http://msdn.microsoft.com/en-us/library/system.data.datacolumn.datatype%28v=vs.110%29.aspx for additional column types.

After you’ve created your table with the columns you want, you’ll need to start populating the table. The code below will show you how to add a row to the table, and assumes there are 3 columns, Name, Department, and Email.

$row = $Table.NewRow()
$row.’Name’ = “John”
$row.’Department’ = “IT”
$row.’Email’ = “John@intrntpirate.com”
$Table.Rows.Add($row)

If you find yourself needing to edit  a row, you have two ways IMO that you can do this. The first way is to explicitly call a particular row. You would do this by calling:

$Table.Rows[0]

In the above, the [0] references the first row. Using [1] would reference the second row, and so on. If you were to run:

$Table.Rows[0].Name

You would get the value for the Name column of row 1 ([0]) returned. You can do the following to change the value.

$Table.Rows[0].Name = “NewName”

To delete the row you would run:

$Table.Rows[0].Delete()

The second way you can change a value is a bit more useful. You can essentially search the table for the row you want, and then change the value of a column.

To do this you use Where-Object. The following example will change the department column for the row where the name is equal to “Joe”.

($Table.Rows | Where-Object {($_.Name -eq “Joe”)}.Department = “NewDepartment”

Finally, if you find yourself needing to delete a row you can do so using the same two methods we used for modifying a row/column. You can delete a row by running:

$Table.Rows[0].Delete()

and also by doing:

($Table.Rows | Where-Object {($_.Name -eq “Joe”)}).Delete()

Managing NPS using PowerShell and netsh

The following post will detail how you can use a combination of PowerShell and netsh to manage one or more Microsoft NPS servers (Server 2008 R2). In the example I’ll detail how you can update the shared secret on all the RADIUS clients across all your NPS servers.

The first thing you need to do in such a PS script is to have a way of identifying your NPS servers. If you’re running a small environment where all the NPS servers are going to need to be setup the same way, then this is easy and I’ll explain what I’ve done below. If you’re running a larger more complex environment where you have groups of NPS servers setup differently, then you’re likely going to want to create some config files for your script to pull server names from.

Assuming you have a small environment… make sure you have your NPS servers registered. To register an NPS server, launch the ‘Network Policy Server’ console, right click the top node ‘NPS (local)’ and click ‘Register server in Active Directory’. This will cause the servers computer account in Active Directory to be added to the security group ‘RAS and IAS Servers’. You can then have your PS script pull a list of NPS servers from this group using a line like

$NPSServers = Get-ADGroupMember “RAS and IAS Servers”

The next step is to build an array of all the Radius clients you have on your NPS servers. Ideally you would already have the same Radius clients on each NPS server, however if you happened to have a Raidus client setup on one NPS server and not on another it’s not a super big deal in regards to having the script update the shared secret.

To build the array of the Radius clients, the first thing you’ll want to do is create the array object.

$ClientArray = @()

You then will want to do a ForEach on your list of NPS servers and collect an export of the Radius clients using netsh. The below can be used to do this.

$NPSServers | ForEach-Object {
$Server = $_.Name
$Result = Invoke-Command -ComputerName $Server -ScriptBlock {
netsh nps show client
}

This will populate the variable Result with an export of all the clients. You’ll then have to parse through the result variable and pull out the client information. The below shows a way (maybe not the best way) to pull out the name of each client on the NPS server, along with the other information on the client.

$Result|ForEach-Object {

$currenttext=$_

If ($currenttext-like“*Client configuration*”) {

$nameline= ($currentline+ 2)

$name=$result[$nameline]

$IPline= ($currentline+ 3)

$IP=$result[$IPline]

$stateline= ($currentline+ 4)

$State=$result[$stateline]

$secretline= ($currentline+ 5)

$Secret=$result[$secretline]

$name=$name.split(“=”)

$name=$name[1]

$IP=$IP.Split(“=”)

$IP=$IP[1]

$State=$State.split(“=”)

$State=$State[1]

$Secret=$Secret.Split(“=”)

$Secret=$Secret[1]

$String=“$Name”

If ($Global:ClientArray-contains$String) {

#skip

}

Else {

$Global:ClientArray+=$String

}

}

$currentline++

}

Just for the record, I’m well aware that the above code could be improved upon. I just copy and pasted that from the first version of a utility I wrote and have never gone back to review and clean it up. It works, so… yeah.

After the above has been ran against each NPS server, you’ll have an array that contains a list of Radius clients without any duplicate names. You can then run the following to go through each client in the array on each server and update the shared secret.

$ServerLoop= 1

$ServerArray|ForEach-Object {

$CurrentServer=$_

Log (“Currently processing NPS server $CurrentServer.”)

$ClientLoop= 1

$Global:ClientArray|ForEach-Object {

Status (“Client $ClientLoop of $ClientCount. Server $ServerLoop of $ServerCount.”)

$Name=$_

Log (“Updating secret on RADIUS client $Name on $CurrentServer.”)

$Result=Invoke-CommandComputerName $CurrentServerScriptBlock {

$Name=$args[0]

$Secret=$args[1]

netsh nps set client name =$Name sharedsecret =$Secret

} ArgumentList $Name, $NewSecret

If ($Result-like“*Ok.*”) {

Log (“The client $Name successfully received the new secret on $CurrentServer.”)

}

Else {

Log (“ERROR: A problem occured while updateing the secret for $Name on $CurrentServer.”)

$Issues=$true

}

$ClientLoop++

}

$ServerLoop++

}

 

As you can see, the main thing to note is the use of netsh to specify the name of a Radius client, and then specify the new shared secret. Below are some other netsh commands you can use to manage your Radius clients using PS scripting like what you saw above.

Add a client

netsh nps add client name = $ClientName address = $ClientIP state = enable sharedsecret = $secret

Edit a client name

netsh nps rename client name = $Name newname = $NewName

Edit other details of a client

netsh nps set client name = $name address = $IP state = $status sharedsecret = $secret

Delete a client

netsh nps delete client name = $Name

 

Alternatively to having a bunch of PowerShell scripts that do this stuff for you, you can create a PS Forms based app that provides you central management of your NPS servers. Below are some screenshots of what I’ve created for the network team at my current employer to use to do day to day tasks with the NPS servers we have.

The screenshot below is of the main page of the app. From here you can add/edit/delete clients, as well as view the radius logs combined from all the NPS servers, update the shared secret across the board, and view information on a specific NPS server.

Main

The screenshot below is of the interface to create a new radius client. When the create button is pressed, PS goes out and creates the new client on all the NPS servers.

Add

The screenshot below is of the interface to edit a radius client. Like when creating a new client, clicking the update button causes PS to go out across all NPS servers and update information for the client on each server.

Edit

The screenshot below is of the interface for syncing an NPS server with another. This could be useful if a single NPS server seems to be out of sync from the others, or if you stand up a new NPS server. (I’m well aware of the ability to export your NPS config and then import it on new NPS servers.)

Sync

The screenshot below is the first of several interfaces for viewing the NPS logs. The screenshot below just shows a list of all the dates there are logs for on all the NPS servers. Upon clicking a date, the screen would update with a list of all the systems that have connected to the NPS servers. Clicking a system would result in a cleaned up view of the NPS logs for that system. Useful when troubleshooting connectivity issues.

Logs1

Profile Picture Manager

Last week I threw together a quick form in PrimalForms that allows users to do the following:

  • View their current AD user profile picture
  • Upload a new picture from a saved JPEG image
  • Use an attached webcam to take a new picture

It can be downloaded HERE.

The code I used for capturing the webcam content was found somewhere online. I didn’t initially plan to post the finished product online, so didn’t think to save the source of code. If I happen to run across the source I’ll post it. UPDATED: SOURCE

The icons used in the form were just ones I found on a Google image search. Most certainly didn’t make them, but seeing how I’m not selling this or making any money off the hits to this site, I don’t see how anyone should have a problem with it.

If you find any problem with the form, or have suggestions for improvements post a comment.

Enjoy!

 

Convert string number to numerical number

Assume that you have a number contained in a variable as a string.

$var = “2”

Now assume you want to use that variable to specify an item in an array, like so:

$array[$var]

In PowerShell v3, you’ll get the expected outcome. However when using the above in PrimalForms (which is based on PowerShell v2) it won’t work. The string based number has to be converted to a numerical number. You can do this by running the following line:

$var = [int]$var

You’ll now have a numerical number stored in your variable.

Query against 389 Directory Services (Open Source LDAP) with PowerShell

Recently I had the need to query an LDAP server running 389 Directory Services with PowerShell. I found my way to this website -> http://mikemstech.blogspot.com/2013/03/searching-non-microsoft-ldap.html#comment-form where you get the basics of connecting to a non-Microsoft LDAP directory using PowerShell, however in my case the steps provided didn’t work. Below are the steps I took in order to successfully query information on user accounts on the 389 Directory Services server in my environment.

I start off in my script with the following lines of code.

[System.Reflection.Assembly]::LoadwithPartialName(“System.DirectoryServices.Protocols”)
[System.Reflection.Assembly]::LoadwithPartialName(“System.Net”)
$c = New-Object System.DirectoryServices.Protocols.LdapConnection “LDAP-SERVER-NAME-HERE”
$c.SessionOptions.SecureSocketLayer = $false
$c.AuthType = [System.DirectoryServices.Protocols.AuthType]::Anonymous
$credentials = New-Object “System.Net.NetworkCredential”
$c.Bind($Credentials)
$basedn = “o=cas.org”
$scope = [System.DirectoryServices.Protocols.SearchScope]::subtree
$attrlist = “*”

The differences between what I did and what mikemstech did was that I disabled SSL and set the AuthType to Anonymous. My 389 DS refused connections if I had those two items set any other way. It’s hard to tell if this was due to it being a 389 DS server, or if it was a unique customization of the environment here.

The next thing I did in my script was create a function for querying user information, however you wouldn’t have to do this.

function Unix-LDAP {
Log (“Beginning Unix-LDAP”)
clear-variable LDAPuidNumber, filter, r, re
Clear-Variable TermEmp, ADsamAccountName, FoundUnix -Scope Global
$LDAPuidNumber = $args[0]
Log (“The UID number being queried in Unix LDAP is $LDAPuidNumber.”)
Log (“The ADsamAccountName variable is currently $Global:ADsamAccountName.”)
$filter = “(uidNumber=$LDAPuidNumber)”
$r = New-Object System.DirectoryServices.Protocols.SearchRequest -ArgumentList $basedn,$filter,$scope,$attrlist
$Error.Clear()
$Global:TermEmp = 0
$Global:FoundUnix = 0
$re = $c.SendRequest($r)
$Global:ADsamAccountName = $re.entries.attributes.uid[0]
$Error
If ($Error[0] -like “Cannot index into a null array.*”) {
Log (“ERROR: The user no longer has a Unix ID. Likely a terminated employee.”)
$Global:TermEmp = 1
}
Else {
Log (“The Unix-LDAP query returned a value of $Global:ADsamAccountName.”)
$Global:FoundUnix = 1
}
Log (“Finished Unix-LDAP”)
}

So the important lines from above are lines 8, 9, 14, and 15. In my case I was query users based upon their uidNumber, however you could replace uidNumber in line 8 with any other property you wanted to query on.

On line 14 you’ll see that at the end of $re.entries.attributes.uid I added [0]. This was something not mentioned on the site I linked to above that I ended up figuring out. Once again, I’m not sure if this is just a difference in how 389 DS operates, or a special configuration in my environment.

You’ll also see that I work with the $Error variable. I do this so that I can determine if the query succeeded or not.

Hopefully the above will save someone else out there a bunch of time.