Posts RSS Comments RSS 253 Posts and 411 Comments till now

How to find extended rights that apply to a schema class object (remix)

The AD guys posted a really cool post about getting extended rights via the schema. That post can be found here:
How to find extended rights that apply to a schema class object

If you dont have Windows 7, 2008 R2, or the Active Directory Management Gateway then you can get similar results by using my functions from here: Getting AD Schema information from Powershell. This works on all versions of AD.

Using these functions you can just do this:

Get-ADSchemaClass group | %{$_.DefaultObjectSecurityDescriptor} | %{$_.access} | ?{$_.ActiveDirectoryRights -eq "ExtendedRight"}

Testing AD LDS (ADAM) replication with Powershell

Earlier this month I had a discussion with Laura (of AD Cookbook fame) regarding ADLDS and how to test convergence. After a few minutes I remembered I had a AD convergence script I wrote a while back found HERE. With a little tweaking (specifically discoverability) we converted it to test ADLDS as well. Below you will fine the result.

Parameters
Server: The ADLDS/ADAM server that hosts the application partition you want to test
DN: The distinguished name of the application partition you want to test (will try to discover)
Port: Port ADLDS/ADAM list on (Default 389)
Table [switch]: A switch that outputs an object with the results.

Note: Please feel free to provide any feedback you have regarding this. I do not use ADLDS or ADAM so other than my test environment I really cannot play with this.

The Code
Test-LDSReplication.ps1


 

Param($Server = $Env:ComputerName,
      $DN,
      $Port = "389",
      [switch]$table
      )

function Ping-Server {
   Param([string]$srv)
   $pingresult = Get-WmiObject win32_pingstatus -f "address=’$srv’ and Timeout=1000"
   if($pingresult.statuscode -eq 0) {$true} else {$false}
}

$DirectoryServer = "{0}:{1}" -f $Server,$Port

$Context = new-object System.DirectoryServices.ActiveDirectory.DirectoryContext("DirectoryServer",$DirectoryServer)
$ADAM = [System.DirectoryServices.ActiveDirectory.AdamInstance]::GetAdamInstance($context)

if(!$DN)
{
    $AppPartition = $ADAM.ConfigurationSet | %{$_.ApplicationPartitions} | Select-Object -first 1
    $DN = $AppPartition.Name
    $dclist = $AppPartition.DirectoryServers | ?{$_.HostName -notmatch $Server}
}
else
{
    $dclist = $ADAM.ConfigurationSet.AdamInstances | ?{($_.Partitions -contains $DN) -and ($_.HostName -notmatch $Server)}
}

if($table)
{
    $DCTable = @()
    $myobj = "" | select Name,Time
    $myobj.Name = ("$Server [SOURCE]").ToUpper()
    $myobj.Time = 0.00
    $DCTable += $myobj
}

$timestamp = [datetime]::Now.ToFileTime().ToString()
Write-Host "`n  Modifying wwwHomePage Attribute on Object [$DN] on [$DirectoryServer] with value [$timestamp]"
$object = ([ADSI]"LDAP://$DirectoryServer/$DN")
$object.wWWHomePage = $timeStamp
$object.SetInfo()
$objectDN = $object.distinguishedname
Write-Host "  Object [$objectdn] Modified! `n"

$start = Get-Date

$i = 0

Write-Host "  Found [$($dclist.count)] LDS replicas"
$cont = $true

While($cont)
{
    $i++
    $oldpos = $host.UI.RawUI.CursorPosition
    Write-Host "  =========== Check $i ===========" -fore white
    start-Sleep 1
    $replicated = $true
    foreach($dc in $dclist)
    {
        if($server -match $dc.HostName){continue}
        if(ping-server $dc.HostName)
        {
            $DCServer = "{0}:{1}" -f $dc.HostName,$dc.LdapPort
            $object = [ADSI]"LDAP://$DCServer/$dn"
            if($object.wwwHomePage -eq $timeStamp)
            {
                Write-Host "  – $DCServer Has Object Description [$dn]" (" "*5) -fore Green
                if($table -and !($dctable | ?{$_.Name -match $dc.HostName}))
                {
                    $myobj = "" | Select-Object Name,Time
                    $myobj.Name = $dc.HostName.ToUpper()
                    $myobj.Time = ("{0:n2}" -f ((Get-Date)$start).TotalSeconds)
                    $dctable += $myobj
                }
            }
            else{Write-Host "  ! $($dc.HostName.ToUpper()) Missing Object [$dn]" -fore Red;$replicated  = $false}
        }
        else
        {
            Write-Host "  ! $($dc.HostName.ToUpper()) Failed PING" -fore Red
            if($table -and !($dctable | ?{$_.Name -match $dc}))
            {
                $myobj = "" | Select-Object Name,Time
                $myobj.Name = $dc.HostName.ToUpper()
                $myobj.Time = "N/A"
                $dctable += $myobj
            }
        }
    }
    if($replicated){$cont = $false}else{$host.UI.RawUI.CursorPosition = $oldpos}
}

$end = Get-Date
$duration = "{0:n2}" -f ($end.Subtract($start).TotalSeconds)
Write-Host "`n    Took $duration Seconds `n" -fore Yellow

if($table){$dctable | Sort-Object Time}

Test-CitrixHotfix

I often find the need to compare the state of my PS servers. This little script allows me to find all the Citrix hotfixes for a group of server(s), or list of server(s) that have a specific hotfix.

Parameters:
$File: The name of the file which contains a list of servers
$Filter: Name or Regex of the Hotfix(es) to return
$Server: Name of the server to check

What it returns (a custom object):
ServerName: Name of the Server with the Hotfix(es)
Hotfixes: List of hotfix names [String[]]
RawObjects: Array of MFCOM MetaFrameHotfix objects

Examples
To List all the Hotfixes on a list of servers
.\Test-CitrixHotfix.ps1 ServerList.txt

To List a Specific hotfix on a list of servers
.\Test-CitrixHotfix.ps1 ServerList.txt -filter PSE450W2K3021

To List all the hotfixes on a specific Server
.\Test-CitrixHotfix.ps1 -s Server1

To List a specific hotfix for a specific server
.\Test-CitrixHotfix.ps1 -s Server1 -filter PSE450W2K3021

The Code:

Param($file,$filter=".*",$server)

Function Ping-Server {
   Param([string]$server)
   $pingresult = Get-WmiObject win32_pingstatus -f "address=’$Server’"
   if($pingresult.statuscode -eq 0) {$true} else {$false}
}
Function Check-CitrixHotfix{
    Param([string]$server)
    $mf = New-Object -ComObject MetaframeCOM.MetaframeFarm
    $mf.Initialize(1)
    $srv = $mf.GetServer2(6,$server)
    $list = $srv.winServerObject2.hotfixes | ?{$_.Name -match $filter}
    $list
}

if($file -and (Test-Path $file))
{
    $servers = cat $file
}
if($input)
{
    $servers = $input
}

if($server){$servers += $server}

foreach($srv in $servers)
{
    if(Ping-Server $srv)
    {
        $hotfixes = Check-CitrixHotfix $srv
        $myobj = "" | Select-Object ServerName,Hotfixes,RawObjects
        $myobj.ServerName = $srv
        $myobj.Hotfixes   =  $hotfixes | %{$_.Name}
        $myobj.RawObjects = $hotfixes
        $myobj
    }
    else
    {
        write-host $srv
        write-host "————"
        write-host "Server not pingable"
    }
}

userAccountControl and “User cannot change password”

Someone asked me a question about setting the “User cannot change password” check box in ADUC. They were creating the user account and setting PASSWD_CANT_CHANGE along with other settings (see my post about HERE about userAccountControl and values) and they couldn’t figure out why the check box wasn’t being applied.

I thought about this, and my first impression was they had the wrong bit value. So, I posted the “correct” one. They came back and said “That didn’t work.”

Hmmm, that’s curious. I turned to Dean and asked him if I was missing something obvious (I had a nagging feeling I had been here before) and he informed me that it has to be set with an “Extended Right” (control access right) via an ACE.

DOH! Now the whole scenario I had THAT feeling about came back to me. I recall having this discussion with someone and providing them a Script that would set the ACE. I searched for the script and I couldn’t find it. This happens to me a lot so I decided a while ago… when I run across this again, BLOG IT!

Technical Info:

In the past, permissions on the ‘userAccountControl’ attribute could be edited oftentimes making the effective password policy moot, i.e. you could end up with accounts that don’t comply with the domain’s password policy.

In Windows 2000, you can’t easily prevent this except by using third party front-end/provisioning tools to manage user objects. In Windows 2003 and later, you can use three newly-added extended rights (Control Access Rights) to prevent these bits from being edited even when the caller has permission to do so. The three (new) ‘Extended Rights’ are –

• Update password not required bit [controls ‘password not required’ and maps to ACE in footnote below]
• Enable per user reversible encryption [controls whether password is stored reversibly encrypted or not]
• Unexpire password [controls ‘password never expires’]

Each of these extended rights MUST be configured on the domain head and scoped as “This Object only” with ALLOW or DENY for the security principals you designate. By default, ‘Authenticated Users’ is granted an ALLOW ACE for each of the three extended rights. This doesn’t mean any old authenticated user can alter the password related bits in the ‘userAccountControl’ attribute; they still require the permission to modify ‘userAccountControl’.

USAGE SCENARIO – create a single group representing ALL three extended rights (or perhaps ONE group for EACH extended right). Then ACL the group(s) accordingly on the domain head with a DENY ACE. Finally, place the account-administrator users and groups that have management permissions to user objects (i.e. they have write permissions to the ‘userAccountControl’ property) in the group(s) you just created thereby preventing those account-administrators from altering the password related bits on the ‘userAccountControl’ attribute resulting in an enforced password policy.

IMPORTANT NOTE [Observed Behavior] – When viewing or changing a user’s ability to change their own password (User Cannot Change Password) through the GUI, it no longer appears to touch ‘userAccountControl’s bit 0x40 (64) — rather, it simply grant’s the ‘SELF’ security principal ‘ALLOW’ or ‘DENY’ to ‘Change Password’ — this can be easily verified by viewing the DACL.

Links:
Modifying User Cannot Change Password (LDAP Provider)

So… Here it is (Set-UserCannotChangePassword.ps1)
Parameters
User: The sAMAccountName of the User
CheckBox: If passed it checks the box
Default: Remove Check box.

Param($User = $(throw ‘$User is Required’,[switch]$CheckBox)
Write-Host

$Searcher = New-Object System.DirectoryServices.DirectorySearcher([ADSI]"","(&(objectcategory=User)(sAMAccountName=$user))")
$MyUser = $Searcher.FindOne().GetDirectoryEntry()

if(!$?){" !! Failed to Get User !!";Return}

if($CheckBox)
{
    Write-Host " – Checking Box for User [$($MyUser.distinguishedName)]"
    $self = [System.Security.Principal.SecurityIdentifier]‘S-1-5-10’
    $ExtendedRight = [System.DirectoryServices.ActiveDirectoryRights]::ExtendedRight
    $deny = [System.Security.AccessControl.AccessControlType]::Deny
    $selfDeny = new-object System.DirectoryServices.ActiveDirectoryAccessRule($self,$ExtendedRight,$deny,‘ab721a53-1e2f-11d0-9819-00aa0040529b’)
    $MyUser.psbase.get_ObjectSecurity().AddAccessRule($selfDeny)
    $MyUser.psbase.CommitChanges()
}
else
{
    Write-Host " – Removing Check Box for User [$($MyUser.distinguishedName)]"
    $ACL = $MyUser.psbase.get_ObjectSecurity().GetAccessRules($true,$false, [System.Security.Principal.NTAccount])
    $ACEs = $ACL | ?{($_.ObjectType -eq ‘ab721a53-1e2f-11d0-9819-00aa0040529b’) -and ($_.AccessControlType -eq ‘Deny’)}
    foreach($ACE in $ACEs){if($ACE){[void]$MyUser.psbase.get_ObjectSecurity().RemoveAccessRule($ACE)}}
    $MyUser.psbase.CommitChanges()
}

Write-Host

Phase 3 of S.DS.P “CSV Output” (as much as 21 sec faster!)

In the next phase I wanted to actually get some data back and display it. Because I just wanted to test performance I kept the test as close as possible. ADFind has the built in ability to output a csv file so I mimicked his output. I also added two properties to return name and sAMAccountName. For those interested, my next step will be to output objects instead of text (really the whole point.)

My expectation for this test was that ADFind would pull ahead due to the processing of each record, but I was pleasantly surprised when the script was actually faster in all environments except 700k Environment. I will let joe explain why his is slower.

Testing
– I ran each 10x in a Row (as joe Suggested) in its own environement
– ADFind I tested using CMD.exe and used ptime.exe for time measurements.
– Get-DSPObject I used Measure-Command and outputed only TotalSeconds

UPDATED!: I had several people suggest that I should have a consolidated view of the results. So here it is. I took the Average from each test and put into and Excel spreadsheet and then plotted on a chart.

Here is the screenshot (lower is better)
DSP vs ADFind Chart

400k objects in a Prod Quality VM running Win2008 RTM 64bit (local)
Average: ADFind = 90.45
Average: DSP = 68.79
Winner: DSP 21.66 secs faster

Get-DSPObject -prop “name”,”sAMAccountName” -csv
Execution time: 70.455s
Execution time: 69.551s
Execution time: 68.466s
Execution time: 68.085s
Execution time: 68.611s
Execution time: 68.179s
Execution time: 68.837s
Execution time: 68.670s
Execution time: 69.105s
Execution time: 67.994s
adfind -b “your dn here” -f “(objectclass=user)” name samaccountname -csv
Execution time: 100.538 s
Execution time: 109.511 s
Execution time: 92.499 s
Execution time: 96.063 s
Execution time: 91.601 s
Execution time: 80.693 s
Execution time: 81.551 s
Execution time: 81.044 s
Execution time: 90.945 s
Execution time: 80.143 s

400k objects in a Prod Quality VM running Win2008 RTM 64bit (remote)
Average: ADFind = 77.18
Average: DSP = 64.23
Winner: DSP 13 secs faster

Get-DSPObject -prop “name”,”sAMAccountName” -csv
Execution time: 67.065 s
Execution time: 63.871 s
Execution time: 63.330 s
Execution time: 63.027 s
Execution time: 62.630 s
Execution time: 64.692 s
Execution time: 64.451 s
Execution time: 64.450 s
Execution time: 64.594 s
Execution time: 64.219 s
adfind -b “your dn here” -f “(objectclass=user)” name samaccountname -csv
Execution time: 77.280 s
Execution time: 77.616 s
Execution time: 77.304 s
Execution time: 76.899 s
Execution time: 77.426 s
Execution time: 76.773 s
Execution time: 76.699 s
Execution time: 77.445 s
Execution time: 77.463 s
Execution time: 76.912 s

700k objects on a Physical machine Win2k3 x86
Average: ADFind = 101.383
Average: DSP = 109.600
Winner: ADFind 8.21 secs faster

Get-DSPObject -prop “name”,”sAMAccountName” -csv
Execution time: 111.088s
Execution time: 109.962s
Execution time: 110.112s
Execution time: 110.832s
Execution time: 110.167s
Execution time: 109.853s
Execution time: 109.757s
Execution time: 110.387s
Execution time: 109.070s
Execution time: 109.065s
adfind -b “your dn here” -f “(objectclass=user)” name samaccountname -csv
Execution time: 101.571 s
Execution time: 101.313 s
Execution time: 101.386 s
Execution time: 101.593 s
Execution time: 101.539 s
Execution time: 100.917 s
Execution time: 101.020 s
Execution time: 101.286 s
Execution time: 101.544 s
Execution time: 101.667 s

300k Objects on Physical Win2k3 x86 (Remote)
Average: ADFind = 49.90
Average: DSP = 44.44
Winner: DSP 5 secs Faster

Get-DSPObject -prop “name”,”sAMAccountName” -csv
Execution time: 44.782 s
Execution time: 44.316 s
Execution time: 44.473 s
Execution time: 44.322 s
Execution time: 44.380 s
Execution time: 44.440 s
Execution time: 44.394 s
Execution time: 44.544 s
Execution time: 44.428 s
Execution time: 44.247 s
adfind -b “your dn here” -f “(objectclass=user)” name samaccountname -csv
Execution time: 51.983 s
Execution time: 51.937 s
Execution time: 51.810 s
Execution time: 49.142 s
Execution time: 48.913 s
Execution time: 49.073 s
Execution time: 48.765 s
Execution time: 49.125 s
Execution time: 49.110 s
Execution time: 49.105 s

200k Objects on my Server at Home (Win2k8 x64)
Average: ADFind = 35.50
Average: DSP = 31.74
Winner: DSP 4 secs Faster

Get-DSPObject -prop “name”,”sAMAccountName” -csv
Execution time: 31.887 s
Execution time: 31.888 s
Execution time: 31.044 s
Execution time: 30.746 s
Execution time: 31.549 s
Execution time: 31.601 s
Execution time: 31.168 s
Execution time: 31.528 s
Execution time: 31.481 s
Execution time: 31.269 s
adfind -b “your dn here” -f “(objectclass=user)” name samaccountname -csv
Execution time: 37.305 s
Execution time: 34.815 s
Execution time: 34.275 s
Execution time: 34.193 s
Execution time: 39.116 s
Execution time: 34.634 s
Execution time: 39.265 s
Execution time: 33.686 s
Execution time: 33.793 s
Execution time: 33.917 s

Here is the script I used for the DSP Tests

function Get-DSPObject {
    Param(
            $filter = "(objectclass=user)",
            $base = ([ADSI]"").distinguishedName,
            $Server,
            [int]$pageSize = 1000,
            [string[]]$props = @("1.1"),
            [switch]$noHeader,
            [switch]$csv,
            [switch]$count
        )
       
    [VOID][System.Reflection.Assembly]::LoadWithPartialName("System.DirectoryServices.Protocols")
       
    [int]$pageCount = 0
    [int]$objcount = 0
   
    if(!$server){$server = ([ADSI]"").distinguishedName -replace  ",","." -replace "dc=","" }
   
    $connection = New-Object System.DirectoryServices.Protocols.LdapConnection($Server)  
    $subtree = [System.DirectoryServices.Protocols.SearchScope]"Subtree"
   
    $searchRequest = New-Object System.DirectoryServices.Protocols.SearchRequest($base,$filter,$subtree,$props)  
    $pagedRequest = New-Object System.DirectoryServices.Protocols.PageResultRequestControl($pageSize)
    $searchOptions = New-Object System.DirectoryServices.Protocols.SearchOptionsControl([System.DirectoryServices.Protocols.SearchOption]::DomainScope)
    $searchRequest.Controls.add($pagedRequest) | out-null
    $searchRequest.Controls.Add($searchOptions) | out-null
   
    # Output Prep
    if($props -notcontains "1.1")
    {
        $MyProps = @()
        foreach($prop in $props){$MyProps += $prop.ToLower()}
        if($csv)
        {
            if(!$noHeader)
            {
                $header = "distinguishedName"
                foreach($prop in $props){$header += ",$prop"}
                $header
            }
        }
        else
        {
            $MyUserObj = New-Object System.Object
            $MyUserObj | Add-Member -name distinguishedName -MemberType "NoteProperty" -value $null
            foreach($prop in $props){$MyUserObj | Add-Member -name $prop -MemberType "NoteProperty" -value $null}
        }
    }
   
    # Process Pages
    while ($True)
    {
        # Increment the pageCount by 1
        $pageCount++
   
        # Cast the directory response into a SearchResponse object
        $searchResponse = $connection.SendRequest($searchRequest)
   
        # Display the retrieved page number and the number of directory entries in the retrieved page
        # "Page:{0} Contains {1} response entries" -f $pageCount,$searchResponse.entries.count
        $objcount += ($searchResponse.entries).count
       
        # Display the entries within this page
        if(!$count)
        {
            if($props -notcontains "1.1")
            {
                foreach($entry in $searchResponse.Entries)
                {
                    if($csv)
                    {
                        $results = "`"{0}`"" -f $entry.distinguishedName
                        foreach($prop in $MyProps)
                        {
                            $results += ",`"{0}`"" -f ($entry.Attributes[$prop][0])
                        }
                        $results
                    }
                    else
                    {
                        $MyUserObj.distinguishedName = $entry.distinguishedName
                        foreach($prop in $MyProps)
                        {
                            $MyUserObj."$prop" = $null
                            $MyUserObj."$prop" = $entry.Attributes[$prop][0]
                        }
                        $MyUserObj
                    }
                }
            }
            else{$searchResponse.Entries | select distinguishedName}
        }
       
        # if this is true, there are no more pages to request
        if ($searchResponse.Controls[0].Cookie.Length -eq 0){if($count){$objcount};break}
   
        # Set the cookie of the pageRequest equal to the cookie of the pageResponse to request the next
        # page of data in the send request and cast the directory control into a PageResultResponseControl object
        $pagedRequest.Cookie = $searchResponse.Controls[0].Cookie
    }
}

foreach($i in (1..10))
{
    $time = Measure-Command { Get-DSPObject -prop "name","sAMAccountName" -csv }
    "Execution time: {0:n3} s" -f $time.TotalSeconds
}

My First Venture into S.DS.P and Powershell

There has be much debate and agony on the the slowness of System.DirectoryServices.DirectorySearcher Class. This has lead me down the path of playing with System.DirectoryServices.Protocols (aka s.ds.p.)

By far the best tool out there right now is ADFind by joe Richards. So I used this for my target (although I didnt expect to get close.)

Here is the Guide I used in my Journey:
Introduction to System.DirectoryServices.Protocols (S.DS.P)

I started by writing the script you will find below and tested in a domain with well over 700k users and one with 200K. I did the test three different times in each Domain changing the order so cache hits wouldn’t be an issue. I also use objectclass on all three so index wouldn’t be a factor. I should NOTE that my script ONLY returns the count ATM. I am going to add properties next.

As you will see by the test results below… I got pretty darn close to adfind.exe in regards to perf. I was quite impressed with S.DS.P. Now to be fair, this was just counting objects. I am sure adfind.exe will start sneaking ahead abit further when we start processing properties and such. Can’t wait to see! Understand these test were just to see what tests I should focus on. I will posting another entry on more extensive count test with just DSP Using 1.1 and Adfind.

Test 1 700K Users done Remote

DirectorySearcher : 125.9123257
ADFind : 46.3763349
DSP Using DN : 69.5628776
DSP Using 1.1 : 49.4458161

Test 2 700K Users done Remote

DirectorySearcher : 125.0230257
ADFind : 46.4486472
DSP Using DN : 68.9255288
DSP Using 1.1 : 49.0780736

Test 3 700K Users done Remote

DirectorySearcher : 125.0230257
ADFind : 47.9162918
DSP Using DN : 79.6885386
DSP Using 1.1 : 54.152966

Test 1 200K done Local

DirectorySearcher : 121.1063569
ADFind : 55.2775406
DSP Using DN : 67.897922
DSP Using 1.1 : 28.5441615

Test 2 200K done Local

DirectorySearcher : 80.0894455
ADFind : 23.558696
DSP Using DN : 54.3111576
DSP Using 1.1 : 42.3485998

Test 3 200K done Local

DirectorySearcher : 99.1125363
ADFind : 80.1497852
DSP Using DN : 64.3716824
DSP Using 1.1 : 64.1940421

Summary: adfind.exe was faster (by bout 4sec on Avg.) remotely and larger domain, but protocals was faster (by bout 8 sec on Avg.) local on smaller domain.
Remote

ADFind: 46.92
DSP Using 1.1: 50.89
DSP Using DN: 72.73
DirectorySearcher: 125.32

Local

DSP Using 1.1: 45.03
ADFind: 52.99
DSP Using DN: 62.19
DirectorySearcher: 100.10

Here the script I ran for the test and how I measured the commands. I am going to play with passing the stats control and see what the server says later.

$SearcherExpression = @’
$searcher = new-object System.DirectoryServices.DirectorySearcher([ADSI]"","(objectclass=user)",@("distinguishedName"))
$searcher.pagesize = 1000
$searcher.findall()
‘@

Write-Host "Test 1"
Write-Host ("-"*40)
$myresults1 = "" | select @{n="DirectorySearcher";e={(Measure-command {invoke-expression $SearcherExpression}).TotalSeconds}},
                         @{n="ADFind";e={(Measure-Command { .\adfind -b "dc=corp,dc=lab" -c -f "(objectclass=user)" }).TotalSeconds}},
                         @{n="DSP Using DN";e={(Measure-command { .\Test-DSProtocals.ps1 }).TotalSeconds}},
                         @{n="DSP Using 1.1";e={(Measure-command { .\Test-DSProtocalsSP.ps1 }).TotalSeconds}}
$myresults1 | fl

Write-Host "Test 2"
Write-Host ("-"*40)
$myresults2 = "" | select @{n="ADFind";e={(Measure-Command { .\adfind -b "dc=corp,dc=lab" -c -f "(objectclass=user)" }).TotalSeconds}},
                         @{n="DSP Using 1.1";e={(Measure-command { .\Test-DSProtocalsSP.ps1 }).TotalSeconds}},
                         @{n="DSP Using DN";e={(Measure-command { .\Test-DSProtocals.ps1 }).TotalSeconds}},
                         @{n="DirectorySearcher";e={(Measure-command {invoke-expression $SearcherExpression}).TotalSeconds}}

$myresults2 | fl

Write-Host "Test 3"
Write-Host ("-"*40)
$myresults3 = "" | select @{n="DSP Using DN";e={(Measure-command { .\Test-DSProtocals.ps1 }).TotalSeconds}},
                         @{n="DSP Using 1.1";e={(Measure-command { .\Test-DSProtocalsSP.ps1 }).TotalSeconds}},
                         @{n="DirectorySearcher";e={(Measure-command {invoke-expression $SearcherExpression}).TotalSeconds}},
                         @{n="ADFind";e={(Measure-Command { .\adfind -b "dc=corp,dc=lab" -c -f "(objectclass=user)" }).TotalSeconds}}
$myresults3 | fl

$myresults1,$myresults2,$myresults3

Here is what the output of that Script looks like

S.DS.P : MyTest.ps1 Output

Here is the System.DirectoryServices.Protocols Code

[System.Reflection.assembly]::LoadWithPartialName("system.directoryservices.protocols") | Out-Null
$domain = ([ADSI]"").distinguishedName -replace  ",","." -replace "dc=",""
$DomainDN = "DC=" + $Domain -replace "\.",",DC="
[int]$pageCount = 0
[int]$pageSize = 1000
[int]$count = 0
$connection = New-Object System.DirectoryServices.Protocols.LdapConnection($domain)  
$subtree = [System.DirectoryServices.Protocols.SearchScope]"Subtree"
$filter = "(objectclass=user)"
$searchRequest = New-Object System.DirectoryServices.Protocols.SearchRequest($DomainDN,$filter,$subtree,@("1.1"))  
$pagedRequest = New-Object System.DirectoryServices.Protocols.PageResultRequestControl($pageSize)
$searchRequest.Controls.add($pagedRequest) | out-null
$searchOptions = new-object System.DirectoryServices.Protocols.SearchOptionsControl([System.DirectoryServices.Protocols.SearchOption]::DomainScope)
$searchRequest.Controls.Add($searchOptions) | out-null

while ($true)
{
    ## increment the pageCount by 1
    $pageCount++
    ## cast the directory response into a
    ## SearchResponse object
    $searchResponse = $connection.SendRequest($searchRequest)
    ## verify support for this advanced search operation
    if (($searchResponse.Controls.Length -lt 1) -or
        !($searchResponse.Controls[0] -is [System.DirectoryServices.Protocols.PageResultResponseControl]))
    {
        Write-Host "The server cannot page the result set"
        return;
    }
    ## cast the diretory control into
    ## a PageResultResponseControl object.
    $pageResponse = $searchResponse.Controls[0]
    ## display the retrieved page number and the number of
    ## directory entries in the retrieved page                    
    #"Page:{0} Contains {1} response entries" -f $pageCount,$searchResponse.entries.count
    $count += $searchResponse.entries.count
    ## display the entries within this page
    ## foreach($entry in $searchResponse.entries){$entry.DistinguishedName}
    ## if this is true, there
    ## are no more pages to request
    if ($pageResponse.Cookie.Length -eq 0){write-Host $count;break}
    ## set the cookie of the pageRequest equal to the cookie
    ## of the pageResponse to request the next page of data
    ## in the send request
    $pagedRequest.Cookie = $pageResponse.Cookie
}

Calculated Properties (UPDATE!)

I had a user point out that my code didnt work in a 4.5 farm. I did some testing and while some information was there, it was not complete. I did Test XP and 4.0 and they worked fine. I created a new one for CTX PS 4.5

$MF = (New-Object -com MetaFrameCOM.MetaFrameFarm)
$MF.Initialize(1)
$MF.Servers | Select-Object ServerName -expand Applications | Select-Object ServerName,DistinguishedName,
  @{n="AppName";e={$_.WinAppObject.BrowserName}},
  @{n=‘Users’; e={$_.LoadData(1) | %{$_.Accounts2} | ?{$_.AccountType -eq 2} | %{$_.AccountName}}},
  @{n=‘Groups’;e={$_.LoadData(1) | %{$_.Accounts2} | ?{$_.AccountType -eq 4} | %{$_.AccountName}}}

Lets talk about whats going on here

First We Get the MFCom Farm Object and Initialize it.

$MF = (New-Object -com MetaFrameCOM.MetaFrameFarm)
$MF.Initialize(1)

Next we iterate throught the servers parsing out only the ServerName and Getting MFCom Application Objects and pipe it along

$MF.Servers | Select-Object ServerName -expand Applications |

The Final part is where we make the changes. In 4.5 the Application object no longer has Users Property or Group Property. It also no longer uses AppName.
For AppName we use: $_.WinAppObject.BrowserName
For Users we use: Accounts2 with AccountType 2
For Groups we use: Accounts2 with AccountType 4
The User/Group Name is stored in a property called AccountName

You will also notice the LoadData. This because not all information is there be default. LoadData fills out the object with data.

 # Get the AppName from BrowserName
  @{n="AppName";e={$_.WinAppObject.BrowserName}},
  # Get users from Accounts2 filtering type 2 and selecting AccountName
  @{n=‘Users’; e={$_.LoadData(1) | %{$_.Accounts2} | ?{$_.AccountType -eq 2} | %{$_.AccountName}}},
  # Get Groups from Accounts2 filtering type 4 and selecting AccountName
  @{n=‘Groups’;e={$_.LoadData(1) | %{$_.Accounts2} | ?{$_.AccountType -eq 4} | %{$_.AccountName}}}

Restart-Service (recursively)

I often need to restart services on multiple machines and one of the biggest issues is the dependent services that also need to be restarted. I finally found time to write a script that will recursively stop and start dependent services.

Parameters:

  • ServiceName: Name of the Service to restart (REQUIRED)
  • Server: Name of the Server to restart the service on (Default is local server)
  • Verbose: Enable verbose output

More Info:
System.ServiceProcess.ServiceController

Restart-Service


Param($ServiceName = $(throw ‘$ServiceName is Required’),
      $Server = $Env:ComputerName,
      [Switch]$Verbose)

$VerbosePreference = "Continue"

[system.Reflection.Assembly]::LoadWithPartialName("System.ServiceProcess") | out-Null

$ErrorActionPreference = "SilentlyContinue"

Write-Verbose " – ServiceName = $ServiceName"
Write-Verbose " – Server = $Server"

function Get-Dependents{
    Param([System.ServiceProcess.ServiceController]$MasterService)
    Write-Verbose "   + Getting Dependent Services for $($MasterService.Name)"
    foreach($dependent in $MasterService.DependentServices)
    {
        Write-Verbose "     – Found Dependent Service [$($dependent.Name)]"
        $dependent
        Get-Dependents $dependent
    }
}

Write-Verbose " – Getting Service [$ServiceName]"
$Service = New-Object System.ServiceProcess.ServiceController($ServiceName,$Server)

Write-Verbose " – Getting Dependent Services"
$DependentServices = Get-Dependents $Service

Write-Verbose " + Stopping [$ServiceName] and dependent Services"
$Service.Stop()

Write-Verbose "   – Waiting for Service to Stop"
$Service.WaitForStatus("Stopped",(new-object system.TimeSpan(0,0,20)))

Write-Verbose " + Starting [$ServiceName]"
$Service.Start()

Write-Verbose "   – Waiting for Service to Start"
$Service.WaitForStatus("Running",(new-object system.TimeSpan(0,0,20)))

foreach($dependent in $DependentServices )
{
    $dependent.Refresh()
    if($dependent.status -eq "Stopped")
    {
        $dependent.Start()
        $dependent.WaitForStatus("Running",(new-object system.TimeSpan(0,0,5)))
    }
}
$Service.Refresh()
$Service
$Service.DependentServices

Run-Command.ps1 : Run External Commands with Power!

I was working late tonight and we had to run a bunch of third party EXEs and such. We do this a good bit so I can’t always avoid calling external executables and I also find psexec.exe much easier than any powershell way to run remote commands. That said I find myself constantly writing this.

$servers = Get-Content $file
foreach($server in $servers)
{
   Do Something Here Like
   psexec \\$server mycmd.exe param1
}

I decided to write a script call Run-Command.ps1.
This will take three parameters
– File (list of servers to process)
– Cmd (Command to run with %S% where you want the server name to be replaced)
– Check (just shows what command would run)
– Will also take Piped Input

Example:
PS> .\Run-Command.ps1 -file c:\serverlist.txt -cmd “psexec \\%S% mycmd.exe Hello World” -check

Run-Command.ps1

Param($file,$cmd,[switch]$whatif,[switch]$verbose)
Begin{
    function Ping-Server {
        Param([string]$srv)
        $pingresult = Get-WmiObject win32_pingstatus -f "address=’$srv’"
        if($pingresult.statuscode -eq 0) {$true} else {$false}
    }
    $servers = @()
}
Process{
    if($_)
    {
        if($_.ServerName){$servers += $_.ServerName}
        else{$servers += $_}
    }
}
End{
    if($file){Get-Content $file | %{$servers += $_}}
    foreach($server in $servers)
    {
        if(ping-server $server)
        {
            if($verbose){Write-Host "+ Processing Server $Server"}
            $mycmd = $cmd -replace "\%S\%",$Server
            if($whatif){Write-Host "  – WOULD RUN $mycmd"}
            else{if($verbose){Write-Host "  – Running $mycmd"};invoke-Expression $mycmd}
        }
        else
        {
            Write-Host "+ $Server FAILED PING" -foregroundcolor RED
        }
    }
}

Backup-EventLog

## UPDATED… ADDED a EVT format Script as Well ##

I saw a post on EE that backups up the eventlogs on Server using VBScript… I wanted to see what I could do with Powershell and this is what I came up with. I put this together pretty quick, so not much Error checking or anything, but the vbscript was 207 lines long without comments.

Basically it does the following
– Takes a BackupLoccation as a Parameter
– Takes a List file or a -FromAD switch
– List gets the computers from a file
– FromAD gets computers from AD
– Creates a Backup folder Named -Logs-
– Like Server1-Logs-09110750
– Processes Each Event Log and backs up to a File
– Clears Log
– I put the output of the script on the bottom.
– NOTE: Security Logs Take awhile. I assume this because I am Generating Events by Reading the Log.

Param($BackupLocation,$list,$FromAD)
function Get-ADComputers{
    $filter = "(&(objectcategory=computer))"
    $root = [ADSI]""
    $props = "dNSHostName","sAMAccountName"
    $Searcher = new-Object System.DirectoryServices.DirectorySearcher($root,$filter,$props)
    $Searcher.PageSize = 1000
    $Computers = $Searcher.findAll() | %{$_.properties[‘dnshostname’]}
    $Computers
}
function Ping-Server {
   Param([string]$server)
   $pingresult = Get-WmiObject win32_pingstatus -f "address=’$Server’"
   if($pingresult.statuscode -eq 0) {$true} else {$false}
}

if($FromAD){$computers = Get-ADComputers}
else{if($list){$computers = get-Content $list}else{Write-Host "Please Provide List";return}}

foreach($computer in $computers)
{
    $Folder = "{2}\{1}-Logs-{0:MMddyymm}" -f [DateTime]::now,$computer,$backupLocation
    Write-Host "+ Processing Server $Computer"
    new-Item $folder -type Directory -force  | out-Null

    if(Ping-Server $computer)
    {
        Write-Host "  + Created Backup Folder $folder"
        $eventlogs = [System.Diagnostics.EventLog]::GetEventLogs($computer)
        foreach($log in $eventlogs)
        {
            $LogFile = "{0}\{1}.csv" -f $Folder,$log.Log
            Write-Host "  + Processing $($log.Log) Log"
            Write-Host "    – Backing up $($log.Log)"
            $logEntries = $log.Entries | %{"{0},{1},{2},{3},{4}" -f $_.TimeGenerated,$_.EntryType,$_.Source,$_.EventID,$_.Message}
            $logEntries | out-File $LogFile -enc ASCII -width 500
            Write-Host "    – Backed up to $logFile"
            Write-Host "    – Clearing Log $($Log.Log)"
            $log.Clear()
        }
        Write-Host
    }
    else
    {
        Write-Host "Server $Computer failed PING!" -foregroundcolor red
    }
}

For those that perfer EVT format and WMI…. I left the Clear part commented

Param($BackupLocation,$list,$FromAD)

function Get-ADComputers{
    $filter = "(&(objectcategory=computer))"
    $root = [ADSI]""
    $props = "dNSHostName","sAMAccountName"
    $Searcher = new-Object System.DirectoryServices.DirectorySearcher($root,$filter,$props)
    $Searcher.PageSize = 1000
    $Computers = $Searcher.findAll() | %{$_.properties[‘dnshostname’]}
    $Computers
}
function Ping-Server {
   Param([string]$server)
   $pingresult = Get-WmiObject win32_pingstatus -f "address=’$Server’"
   if($pingresult.statuscode -eq 0) {$true} else {$false}
}

if($FromAD){$computers = Get-ADComputers}
else{if($list){$computers = get-Content $list}else{Write-Host "Please Provide List";return}}

foreach($computer in $computers)
{
    if(ping-server $computer)
    {
        $Folder = "{1}-Logs-{0:MMddyymm}" -f [DateTime]::now,$computer
        Write-Host "+ Processing Server $Computer"
        New-Item "$backupLocation\$folder" -type Directory -force  | out-Null
        If(!(Test-Path "\\$computer\c$\LogBackups")){New-Item "\\$computer\c$\LogBackups" -type Directory -force | out-Null}
        $Eventlogs = Get-WmiObject Win32_NTEventLogFile -ComputerName $computer
        Foreach($log in $EventLogs)
        {
            $path = "\\{0}\c$\LogBackups\{1}.evt" -f $Computer,$log.LogFileName
            $result = ($log.BackupEventLog($path)).ReturnValue
            Copy-Item $path -dest "$backupLocation\$folder" -force
            #if($result -eq 0){$log.ClearEventLog()}
        }
    }
}

NOTE: Shortly after writing this… I found this little tibit… it seems to have been around since SP3 of Win2000

Found it Here
http://blogs.msdn.com/spatdsg/default.aspx

AutoBackupLogFiles – backs up the event logs “Using this entry causes the Event Log service to automatically clear a full event log and to back up the log file. ”
http://support.microsoft.com/kb/312571

Next »