Thursday, May 06, 2010

Using Powershell to create groups, populate groups and retrieve LDAP distinguished names

Update 20121203 - I just noticed this is still getting hits.  this is largely deprecated, from Powershell 2.0 and beyond, do your self a favor and use the ActiveDirectory module.  if you don't have access to that module for some reason (they do exist) the below should still work.

Update: 20100507 - updated the sam search string to do fuzzy matching.

A couple more functions I needed during a recent project.  Using powershell of course, we needed to create a group in ad in a specific OU.  We also wanted to populate that group.  To do the second, I needed a helper function to get the distinguished name of a group or user.  I leverage two main sites for most of these with some light modifications.   

For create group we pass it the name of our new group and the OU we want to create our group in, such as:
Create-group “Newgroup” “OU=ServerGroups,DC=example,DC=com”

function create-group ($groupname, $strOU) {
      $OU = [adsi]"LDAP://$strOU"
      $group = $ou.Create("Group", "CN=$groupname")
      $group.setinfo()
      $g2 = [adsi]"LDAP://CN=$groupname,$strOU"
      $g2.sAMAccountName = $groupname
      $g2.setinfo()
      Write-Host "Created $groupname in $strOU"
}

For add-usertogroup we pass it a user DN.  DNs are the full LDAP distinguished name like:
LDAP://CN=myuser,OU=Admins,DC=example,DC=com 
So we would pass something like
Add-usertogroup “LDAP://CN=myuser,OU=Admins,DC=example,DC=com” “LDAP://CN=Newgroup,OU=ServerGroups,DC=example,DC=com”

That is kind of annoying so you could also use the get-dn function below. 
The safe way:
$userDN = get-dn "myuser"
$groupDN = get-dn "newgroup"
add-usertogroup $userDN $groupDN

or the one liner
add-usertogroup (get-dn "myuser") (get-dn "newgroup")

function add-usertogroup ($userDN, $groupDN) {
      $user = [adsi]$userDN
      $group = [adsi]$groupDN
      Write-Host "Adding $($user.cn) to $($group.cn)"
      $members = $group.member
      $group.member = $members+$user.distinguishedName
      $group.setinfo()
}

I would further note this excellent site describing how to write an LDAP filter.  It cleared a few things up for me at long last.  I can’t believe I never realized that & was a logical AND...  Pretty straightforward afterwards but the writeup above helped me bridge the gap.  Note that it is for some software or other so ignore the part about escaping the special characters at the top. B)

function get-dn ($SAMName)
{
      $root = [ADSI]''
      $searcher = new-object     System.DirectoryServices.DirectorySearcher($root)
      #note: if you don't want fuzzy searches, remove the *s from the line below.  
      #this will force a match of the search string only - thanks jc for the tip
      $searcher.filter = "(&(|(objectClass=user)(objectClass=group))(sAMAccountName=*$SAMName*))"
      $user = $searcher.findall()
      if ($user.count -gt 1)
      {   
            $count = 0
            foreach($i in $user)
            {
            write-host $count ": " $i.path
            $count = $count + 1
      }
    $selection = Read-Host "Please select item: "
      return $user[$selection].path
      } else {
      return $user[0].path
      }
}

Note that it matches the SAM Account Name (aka the ‘Pre-Windows 2000’ name in the AD snapin)
Enjoy

Wednesday, May 05, 2010

Use powershell to quickly backup all TFS Work Item Types

Hey all,
Just a quickie to easily backup all your current work items from all projects in Team Foundation Server 2008 SP1.  I wanted to do this ahead of a big migration and I am adverse to manual labor.

It starts w/ a function i found a couple years ago.  I don’t recall where or I would give credit here.  It is very useful for any tfs powershell manipulation.

Edit below to enter your $TFSHost and update your path if necessary.  I recommend running from a blank directory.  It will create a directory for each project and export each WIT for that project to the respective directories.

Update: I added another function from my library test-win32 and had that manage the default paths for x64 and x86 archs.


Good luck
$tfshost = "thshost"


 
function Test-Win32() {
    return [IntPtr]::size -eq 4
}

if (test-win32) {
      $tfstoolspath = "C:\Program Files\Microsoft Visual Studio 9.0\Common7\IDE"
} else {
      $tfstoolspath = "C:\Program Files (x86)\Microsoft Visual Studio 9.0\Common7\IDE"
}

function get-tfs {
      #10-08 - cornasdf - i stole this from the web but this is a great way to connect to TFS
      # in your code you use: $tfs =  get-tfs $TFSHost
      #some quick examples from resolvedticketsmail.ps1:
      #get select from Stored query
      #$query = $tfs.WIT.Projects[$ProjectHoldingQuery].StoredQueries | where{$_.Name -eq $QueryViewName}

      #get results of specified query
      #$oldTickets = $tfs.WIT.Query($query.QueryText)



      param(
            [string] $serverName = $(throw 'serverName is required')
      )

      begin
      {
            # load the required dll
            [void][System.Reflection.Assembly]::LoadWithPartialName("Microsoft.TeamFoundation.Client")

            $propertiesToAdd = (
                  ('VCS', 'Microsoft.TeamFoundation.VersionControl.Client', 'Microsoft.TeamFoundation.VersionControl.Client.VersionControlServer'),
                  ('WIT', 'Microsoft.TeamFoundation.WorkItemTracking.Client', 'Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItemStore'),
                  ('CSS', 'Microsoft.TeamFoundation', 'Microsoft.TeamFoundation.Server.ICommonStructureService'),
                  ('GSS', 'Microsoft.TeamFoundation', 'Microsoft.TeamFoundation.Server.IGroupSecurityService')
            )
      }

      process
      {
            # fetch the TFS instance, but add some useful properties to make life easier
            # Make sure to "promote" it to a psobject now to make later modification easier
            [psobject] $tfs = [Microsoft.TeamFoundation.Client.TeamFoundationServerFactory]::GetServer($serverName)
            foreach ($entry in $propertiesToAdd) {
                  $scriptBlock = '
                        [System.Reflection.Assembly]::LoadWithPartialName("{0}") > $null
                        $this.GetService([{1}])
                  ' -f $entry[1],$entry[2]
                  $tfs | add-member scriptproperty $entry[0] $ExecutionContext.InvokeCommand.NewScriptBlock($scriptBlock)
            }
            return $tfs
      }
}    

$tfs = get-tfs $tfshost

$projects = $tfs.wit.Projects


foreach ($proj in $projects) {
      if ( -not (Test-Path -path .\$($proj.Name))) {
            New-Item .\$($proj.Name) -type directory
      }
     
      foreach ($wit in $proj.WorkItemTypes) {
            "Exporting $($proj.Name) : $($wit.Name) -> .\$($proj.Name)\$($wit.Name).xml"
            & "$tfstoolspath\witexport.exe"  /p "$($proj.Name)" /n "$($wit.Name)" /f ".\$($proj.Name)\$($wit.Name).xml" /t $tfshost
           
      }
}

Tuesday, April 20, 2010

Systems Center Operations Manager R2 CU1 Issues and command line update

Just a quick one on our recent opsmgr R2 CU1 update. 

Kevin Holman has a good post about his experiences.  Start here.  I would add the below

1.  The gateway update does not copy the update msp to the agentmanagement folder.  Due to this, when I approved updates for agents on the other side of a gateway, they did not get the update.  You could fix this by copying the files from your gateway server before you approve your pending agent updates.  Copy the files at c:\Program Files\System Center 2007 R2 Hotfix Utility\KB974144\agent to the appropriate directory under c:\Program Files\System Center Operations Manager 2007\AgentManagement.  Make sure you copy the x86 update to the x86 folder.  x64 goes in AMD64 and ia64 goes in ia64.

2.  Only remotely managed agents will offer an update.  In my case, my only agents that were manually installed were due to troubleshooting or other issues.  Another solution from Kevin Holman shows how to reset your agents to be remotely managed.  Pay attention to the whole post to make sure it is right for you.

3.  If you didn’t realize your gateways were not updated (like me) or put in the wrong password for one of your domains, you may need to update manually.  You can go on each server and run the installer and then choose install agent but that seemed annoying.  You can do it from the server over the network w/:

"\\GATEWAYSERVER\c$\Program Files\System Center 2007 R2 Hotfix Utility\KB974144\SetupUpdateOM.exe" /silent /x86msp:KB974144-x86.msp /amd64msp:KB974144-x64.msp /UpdateAgent

4.  or you can do what I did and psexec it w/:

psexec -u DOMAIN\user "\\comp1,comp2,compN" cmd /c "\\GATEWAYSERVER\c$\Program Files\System Center 2007 R2 Hotfix Utility\KB974144\SetupUpdateOM.exe" /silent /x86msp:KB974144-x86.msp /amd64msp:KB974144-x64.msp /UpdateAgent

Note that you need to pass the user to psexec (which will query for a password) so you can access network resources.  Also note that I needed to enclose my computer list in “ to get it to pass correctly.  YMMV.

Also note that this will not work w/ 2k8+ clients as UAC gets in the way.  You could add –s to the the psexec line but then you would need a file share that the machine accounts could get at.  You could also probably do a small script that did a net use and then accessed the files.  I cheesed out and logged on directly as I only had a few that needed to be done manually. 

Good luck.

Thursday, April 15, 2010

How does windows determine a cipher strength for an encrypted connection OR SQL Server data in transit cipher strength

This question is really just “how does windows determine a cipher strength for an encrypted connection” as SQL server just hands off to the windows schannel.dll to deal w/ this. But I was looking to determine the answer for SQL Data in transit encryption.

This was pretty muddy to track down. There are some reference details here, here and here.

At the end of the day, it comes down to an OS version question (potentially influenced by some OS/registry settings or patches). I couldn’t find any matrix anywhere so I am going to start one here. I am going to start out w/ the few I tested and hopefully fill this in w/ reader submissions (hint, hint).  The guidance I can find seems to say that Win 2k8R2 and Win 7 have the same schannel capabilities so I am going to list them together. 

Client\Server Windows 2008 R2 Windows 2003 SP2 Windows 2000 SP4
Windows 2008R2/7 TLS_RSA_WITH_AES_128_CBC_SHA SSL_RSA_WITH_RC4_128_MD5 SSL_RSA_WITH_RC4_128_MD5
Windows 2003 SP2 SSL_RSA_WITH_RC4_128_SHA SSL_RSA_WITH_RC4_128_MD5 SSL_RSA_WITH_RC4_128_MD5
Windows XP SP3 SSL_RSA_WITH_RC4_128_SHA SSL_RSA_WITH_RC4_128_MD5 SSL_RSA_WITH_RC4_128_SHA
How to use this table: Find the client OS listed vertically down the left side and then find the server OS listed horizontally across the top.  Where the row and column meet is your the cipher which, under the default settings, they should negotiate to use. More on the ciphers here.

How did we determine this? The only way I was able to find was to sniff the beginning of the SSL conversation. Netmon 3.3 is an excellent tool for this.  Note that you may need to set your Windows parsers from stub to full in order to decrypt the TDS packets.  You can do this in Tools -> options -> Parser. Set Windows to ‘Full’

image

Start your trace before you start your encrypted connection. I like netmon b/c it will separate the network traffic into conversations. Find the conversation you are looking for by IP and port. For my purposes I was looking for my web server IP and my DB IP going to port 1433 on my DB.

Once you have found your conversation, you are going to want to find the SSL conversation. In netmon, it will look like this:
clip_image001

What happens here, generally, is that the client offers a list of supported ciphers in the SSL: Client Hello. In netmon, you can see this by selecting that packet as I have done above. In the ‘Frame details’ pane, you can expand ssl: Client Hello.–> TlsRecordLayer: –> SSLHandshake –> Client Hello:.  You should see a list of CipherSuites listed in order of preference.  See the bottom of the picture below.
image

The server will respond with cipher it wants.  You can see this in the (likely) next packet under ssl –> TlsRecofdLayer-> SSLHandshake –> ServerHello –> CipherSuite.  In this case, both client and server are running Win 2k8 r2.
image

As you can see, between Win 2k8r2 and Win 2k8r2 we attain TLS_RSA_WITH_AES_128_CBC_SHA.
There are several methods to influence the SSL Handshake.  The links above will be useful in starting that journey.

One note on AES-128 cipher strength.  For a measure of scale, a computer with a billion processing elements, each capable of trying a billion keys every second, would be able to try (2^60 keys/second).  That means it would take (2^128 keys) / (2^60 keys/second) = 2^68 seconds to brute-force check all 128-bit keys. That is about 10^13 years to crack the key, or about 1000 times the age of the universe. Realistically, you would only need to try half the keys on average so that would be 500 times the age of the universe.

Or you could find a flaw in the algorithm...
or steal the certificate/password...

Wednesday, March 31, 2010

Using powershell to get ntfs info (such as cluster size)

20100331 - slight update.  Converting the hex items to dec and allowing passing of multiple drives.
20131230 - slight update.  Changed the convert to use an Int64 instead of int32 as I was getting overflow on larger drives

A new function for my library.   Base idea copped from Jacques Barathon [MS] at the bottom of this thread and hashed out to get all the properties.


function get-ntfsinfo {
      param ([char[]]$drive = "c")
     

      $drive | foreach {
            if (test-path "$($_):") {
                  $cs = new-object PSObject
                  $cs | add-member NoteProperty Drive $_
                  $output = (fsutil fsinfo ntfsinfo "$($_):")
                  foreach ($line in $output) {
                        $info = $line.split(':')
                        #if the value is hex, convert to dec and put hex in ()
                        if ($info[1].trim().startswith('0x0')) {
                              $info[1] = [Convert]::ToInt64(($info[1].Trim()),16).toString() + " (" + $info[1].Trim().toString() + ")"
                        }
                        $cs | add-member NoteProperty $info[0].trim().Replace(' ','_') $info[1].trim()
                        $info = $null
                  }
                  $cs
            } else {
                  throw "Drive '$_' not found"
            }
      }
}


A basic run gives you:
PS C:\Windows\system32> get-ntfsinfo e


Drive                           : e
NTFS_Volume_Serial_Number       : 0xeabe7aefbe7ab423
Version                         : 3.1
Number_Sectors                  : 558614527 (0x00000000214bc7ff)
Total_Clusters                  : 69826815 (0x00000000042978ff)
Free_Clusters                   : 36249097 (0x0000000002291e09)
Total_Reserved                  : 0 (0x0000000000000000)
Bytes_Per_Sector                : 512
Bytes_Per_Cluster               : 4096
Bytes_Per_FileRecord_Segment    : 1024
Clusters_Per_FileRecord_Segment : 0
Mft_Valid_Data_Length           : 262144 (0x0000000000040000)
Mft_Start_Lcn                   : 786432 (0x00000000000c0000)
Mft2_Start_Lcn                  : 2 (0x0000000000000002)
Mft_Zone_Start                  : 786496 (0x00000000000c0040)
Mft_Zone_End                    : 837664 (0x00000000000cc820)
RM_Identifier                   : 04E578BF-2D2F-11DF-9782-18A9055883A2


But the nice thing here is being able to say
PS C:\Windows\system32> (get-ntfsinfo e).Bytes_Per_Cluster
4096


I then looped it to check cluster size (what I really wanted) on each drive on the server.

foreach ($drive in Get-PSDrive | where {$_.Provider.name -eq "FileSystem"}) {
      if ($drive.free -ne $null) {
            "$drive - $((get-ntfsinfo $drive.name).bytes_per_cluster)"
      }
}

gives output like:

C - 4096
E - 4096
G - 65536
J - 4096
K - 4096
L - 4096
M - 65536
N - 65536
O - 65536
Q - 4096
S - 65536
T - 65536
Z – 4096


Let me know if you use it…

analytics