Posts RSS Comments RSS 253 Posts and 411 Comments till now

Blog Archives

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
}

Fun with Active Directory (Playing Around Series)

This is the first in a series of posts call “Playing Around Series.” This series will basically be demo Videos of different Snapins/.NET Classes and their use.

In this entry I run through creating a DirectoryEntry, DirectorySearcher, and using the System.DirectoryServices.ActiveDirectory.Domain Class.

Note: This is a fast run through. I STRONGLY recommend pausing and reading the Comments. Best Viewed Full Screen

Get the Flash Player to see this content.


Special Shout-out to JayKul and Jeffrey Snover for the Start-Demo script.
http://www.powershellcentral.com/scripts/302

Demo Text

#
# Lets start off by looking at DirectoryEntry
#
$DE = New-Object System.DirectoryServices.DirectoryEntry("LDAP://CN=tstUsr101,OU=MyUsers,DC=corp,DC=lab")
#
# First lets see what we have access to
#
$DE | Get-Member
#’
# Hmmm.. doesn’t seem like much. OH WAIT! Remember Powershell abstracts the class… Lets add psbase
#
$DE.psbase | Get-Member
#
# Lets look at what properties are available.
#
$DE.psbase.Properties
#
# Thats more like it. You may also note that some AD properties are still missing.
# That is because that LDAP doesnt return all the properties. For these you need to "GET" them.
$DE.psbase.InvokeGet(‘msExchUMFaxID’)
#
# Using DirectoryEntry is fine if you know the DN of the object, but what if you need to search?
# Lets look at System.DirectoryServices.DirectorySearcher
#
# The Searcher needs some info so put that in variables first
#
$root = [ADSI]""  ## This is using the Type Accelerator we spoke about earlier… This is Gets the base
$filter = "(&(objectcategory=user))"
#
# Now Lets create the searcher
#
$searcher = New-Object System.DirectoryServices.DirectorySearcher($root,$filter)
#
# That gets the searcher ready, but to execute we need to call findall() or findone()
#
$users = $searcher.findAll()
#
# Lets see what we got. We have alot so lets only pick the first 10
#
$users | select -first 10
#
# Tons of info, but notice that this is NOT the same as DirectoryEntry
#
$users | get-Member
#
# It still has the properties property, Lets look (but only the first 3)
#
$users | select -first 3 | %{$_.Properties}
#
# Finally Lets look at System.DirectoryDervices.ActiveDirectory.Domain
#
# We can use this to interactively browse around
#
[system.directoryservices.activedirectory.domain]::GetCurrentDomain()
#
# Lets assign that to variable to play with
#
$domain = [system.directoryservices.activedirectory.domain]::GetCurrentDomain()
$domain
#
# Lets see what this has to offer
#
$domain | get-member
#
# Tons of cool stuff here.
#
# We can find all domain controllers
$domain.FindAllDomainControllers()
#
# We Can look at our Domain FSMO
#
$domain | ft PdcRoleOwner,RidRoleOwner,InfrastructureRoleOwner
#
# I can even step the tree and get my forest root
#
$forest = $domain.Forest
$forest
#
# With our new found $forest object… what can do we do?
#
$forest | Get-Member
#
# WE can find all our GCs
#
$forest.FindAllGlobalCatalogs()
#
# We can look at the Forest Mode
#
$forest.ForestMode
#
# Look at the Forest FSMO
#
$forest | ft SchemaRoleOwner,NamingRoleOwner
#
# Even look at sites
$forest.Sites
#
# We can go on forever and ever. If you would like we can revisit this later.
#

Citrix Script Repo (aka Exchange)

I was recently directed to a new website (for me at least) that contains a repository of Citrix related scripts. There are some pretty useful scripts posted (and I started adding some Powershell ones.) You should check it out

Script Exchange
http://community.citrix.com/display/cdn/Script+Exchange

I would also recommend RSS’ing Vishal blog (Dev at Citrix.) He has a passion for Powershell and I hope to see some awesome things from him in the future.

Vishal Ganeriwala’s Blog
http://community.citrix.com/blogs/citrite/vishalg/

Get-CitrixAppServer.ps1 (Citrix Top 10)

This is pretty simple script. It is actually just an adaption from my Get-CitrixApplication.ps1 script posted at the bottom. There is a moment in time when you start thinking in terms of objects and stop thinking in terms of text output you are looking for.

This will make a huge difference in productivity and take you from constantly writing scripts to interactively getting the information you are after, but until you make the transition I will continue to provide scripts that do both 🙂

# Get-CitrixAppServer.ps1
# Brandon Shell [MVP]
# www.bsonposh.com
# Gets All the Servers for Specific App
Param($app)
$mfApp = New-Object -ComObject MetaFrameCOM.MetaFrameApplication
$mfApp.Initialize(3,$app)
$mfApp.LoadData(1)
$mfApp.Servers | %{$_.ServerName}

Here is the script that I use to return an Application object.

# Get-CitrixApplication.ps1
# Brandon Shell [MVP]
# www.bsonposh.com
# Gets a Citrix Application Object.
Param($app)
$mfApp = New-Object -ComObject MetaFrameCOM.MetaFrameApplication
$mfApp.Initialize(3,$app)
$mfApp.LoadData(1)
$mfApp

Then from the commandline I would just do this
PS> Get-CitrixApplication.ps1 “Applications\MyApp1” | %{$_.Servers} | %{$_.ServerName}

While this looks more complicated it is more versatile and easy to change. Lets say I want current User for this app instead.
PS> Get-CitrixApplication.ps1 “Applications\MyApp1” | %{$_.Sessions} | %{$_.UserName}

Find-CitrixIdleUser.ps1 (Citrix Top 10)

This is one of the more useful scripts (at least for me.) It will query the farm and tell you all the user who have exceeded the idle time specified. I also combined another script that logged off users.

Name: Find-CitrixIdleUser.ps1
Purpose: Finds users with idle time greater than value passed and logs them off if -logoff is passed

# Find-CitrixIdleUser.ps1
# Brandon Shell [MVP]
# www.bsonposh.com
# Finds users with idle time greater than value passed
Param($time,[switch]$day,[switch]$hour,[switch]$minute,[switch]$logoff,[switch]$verbose,[switch]$help)
function HelpMe{
    Write-Host
    Write-Host " Find-CitrixIdleUser.ps1:" -fore Green
    Write-Host "   Finds users with idle time greater than value passed"
    Write-Host
    Write-Host " Parameters:" -fore Green
    Write-Host "   -Time                  : Optional. Server to Get Print Info From "
    Write-Host "   -Day                   : Optional. Checks Days of Idle Time "
    Write-Host "   -Hour                  : Optional. Checks Hours of Idle Time (Default)"
    Write-Host "   -Minute                : Optional. Checks Minutes of Idle Time"
    Write-Host "   -Logoff                : Optional. Logs User off if Idle Time exceeds Time"
    Write-Host "   -Verbose               : Optional. Show Verbose Output"
    Write-Host "   -Help                  : Optional. Displays This"
    Write-Host
    Write-Host " Examples:" -fore Green
    Write-Host "   Finds Users who have been idle 2 hours and return AppName, UserName, SessionID, and ClientName" -fore White
    Write-Host "     Find-CitrixIdleUser.ps1 2 -hour | ft AppName,UserName,SessionID,ClientName -auto" -fore Yellow
    Write-Host
    Write-Host "   Finds Users who have been idle 2 hours and Logs them Off" -fore White
    Write-Host "     Find-CitrixIdleUser.ps1 2 -hour -logoff" -fore Yellow
    Write-Host
}
if($verbose){$verbosepreference = "Continue"}
Write-Host
if(!$time -or $help){helpme;Write-Host;Return}

# Get Citrix Farm Object
Write-Verbose " – Creating MFFarm Object"
$farm = new-Object -com "MetaframeCOM.MetaframeFarm"
$farm.Initialize(1)

# Parse Sessions
Write-Verbose " – Parsing Sessions. Total of [$($farm.Sessions.count)] Sessions"
foreach($session in $farm.Sessions)
{
    Write-Verbose "   – Processing Session ID [$($Session.SessionId)]"
    $shouldLogOff = $false

    # Getting Citrix Session Idle Time and Convert to System.DateTime
    Write-Verbose "   – Getting LastInputTime"
    $ctxDate = $session.LastInputTime(1)
    Write-Verbose "   – Checking if idle time is -gt 0"
    if(($ctxDate.HighPart -ne 0) -and ($ctxDate.LowPart -ne 0))
    {
        $date = "{0,4}{1,2:00}{2,2:00}{3,2:00}{4,2:00}" -f $ctxDate.year,$ctxDate.Month,$ctxDate.Day,$ctxDate.Hour,$ctxDate.Minute
        Write-Verbose "   – Converted LastInputTime to [$date]"
        $SessionIdleTime = [system.DateTime]::ParseExact($date,‘yyyyMMddHHmm’,$null)

        # Get Current Time in System.DateTime
        Write-Verbose "   – Getting Current Date"
        $now = Get-Date
        Write-Verbose "   – Current Date is [$now]"

        # Find Difference
        Write-Verbose "   + Getting Time Difference"
        $diff = $now$SessionIdleTime
        Write-Verbose "     – Found Days [$($diff.TotalDays)] Hours [$($diff.Totalhours)] Minutes [$($diff.TotalMinutes)]"

        # Output Sessions that match
        if($day)   {    if($diff.TotalDays    -gt $time)    {$session;$shouldLogOff = $true}  }
        if($hour)  {    if($diff.Totalhours   -gt $time)    {$session;$shouldLogOff = $true}  }
        if($minute){    if($diff.TotalMinutes -gt $time)    {$session;$shouldLogOff = $true}  }

        Write-Verbose "     – Set `$shouldLogOff to [$shouldLogOff]"

        # Logging Off User
        if($logoff -and $shouldLogOff)
        {
            Write-Verbose "   – Logging Off Session ID [$($Session.SessionId)]"
            $session.Logoff($false)
        }
    }
    else
    {
        Write-Verbose "   – Session ID [$($Session.SessionId)] NOT Idle"
    }
    Write-Host
}

Set-CitrixPSVersion.ps1 (Citrix Top 10)

I am constantly looking for content for my blog and tasks that can be automated. I believe this helps me learn and helps other with their needs.

In this search I was directed to convert/write Powershell examples for scripts located here http://community.citrix.com/display/cdn/Script+Exchange

Here is the first of those scripts.

Name: Set-CitrixPSVersion.ps1
Purpose: Sets PS Version on a Server, List of Servers, or all Servers in the Farm

# Set-CitrixPSVersion.ps1
# Brandon Shell [MVP]
# www.bsonposh.com
# Sets PS Version on a Server, List of Servers, or all Servers in the Farm
Param($file,$server,$PSVer,[switch]$help,[switch]$all,[switch]$whatif,[switch]$show,[switch]$verbose)
function HelpMe{
    Write-Host
    Write-Host " Set-CitrixPSVersion.ps1:" -fore Green
    Write-Host "   Sets PS Version on a Server, List of Servers, or all Servers in the Farm"
    Write-Host
    Write-Host " Parameters:" -fore Green
    Write-Host "   -File <fileName>       : Optional. Name of the File of Servers"
    Write-Host "   -Server <serverName>   : Optional. Name of the Server to Change"
    Write-Host "   -Verbose               : Optional. Enables Verbose Output"
    Write-Host "   -All                   : Optional. Sets Version on all Servers [Requires -Server]"
    Write-Host "   -Show                  : Optional. Displays the Version for Server(s)"
    Write-Host "   -Help                  : Optional. Displays This"
    Write-Host "   -Whatif                : Optional. Will not Commit Info just Display what would change"
    Write-Host
    Write-Host " Examples:" -fore Green
    Write-Host "   Set PS Version on Server1 to STD" -fore White
    Write-Host "     .\Set-CitrixPSVersion.ps1 -Server Server1 -psver STD " -fore Yellow
    Write-Host
    Write-Host "   Set PS Version on Servers in a File" -fore White
    Write-Host "     .\Set-CitrixPSVersion.ps1 -file c:\Mylist.txt -psver STD " -fore Yellow
    Write-Host
    Write-Host "   Get PS Version from ALL server" -fore White
    Write-Host "     .\Set-CitrixPSVersion.ps1 -all -Server myzdcserver" -fore Yellow
    Write-Host
    Write-Host " Product Edition Options" -fore Green
    Write-Host "   STD = Citrix Presentation Server Standard Edition" -fore White
    Write-Host "   ADV = Citrix Presentation Server Advanced Edition" -fore White
    Write-Host "   ENT = Citrix Presentation Server Enterprise Edition" -fore White
    Write-Host "   PLT = Citrix Presentation Server Platinum Edition" -fore White
    Write-Host
}
function Ping-Server {
    Param($srv)
    $pingresult = Get-WmiObject win32_pingstatus -f "address=’$srv’"
    if($pingresult.statuscode -eq 0) {$true} else {$false}
}
function Set-PSVer{
    Param($srv)
    Write-Verbose "  Getting Citrix Server Object"
    $type = [System.Type]::GetTypeFromProgID("MetaframeCOM.MetaframeServer",$srv)
    $mfsrv = [system.Activator]::CreateInstance($type)
    $mfsrv.Initialize(6,$srv)
    if(!$?){Write-Host "   – Server [$srv] threw and Error" -fore red;return}

    if($show) # Check for $Show and will display only
    {
        Write-Host "   – PS Version for [$srv]: $($mfsrv.WinServerObject.mpsedition)"
        return
    }
    else
    {
        if($whatif) # Check for $Whatif
        {
            Write-Host "  What if: Performing operation `"Set PS Version [$PSVer]`" on Target `"$srv`"."
        }
        else
        {
            Write-Verbose "   – Setting PSVer to [$PSVer] on [$srv]"
            $mfsrv.WinServerObject.mpsedition = $PSVer
            Write-Host "   – PS Version for [$srv]: $($mfsrv.WinServerObject.mpsedition)"
        }
    }
}
function Set-PSALL{
    $mfarm = new-Object -com "MetaframeCOM.MetaframeFarm"
    $mfarm.Initialize(1)
    foreach($mfsrv in $mfarm.Servers)
    {
        if($show){Write-Host "   – PS Version for [$($mfsrv.ServerName)]: $($mfsrv.WinServerObject.MPSEdition)";continue}
        if($whatif){Write-Host "  What if: Performing operation `"Set PS Version [$PSVer]`" on Target `"$($mfsrv.ServerName)`"."}
        else
        {
            Write-Verbose "   – Setting PSVer to [$PSVer] on [$($mfsrv.ServerName)]"
            $mfsrv.WinServerObject.mpsedition = $PSVer
            Write-Host "   – PS Version for [$($mfsrv.ServerName)]: $($mfsrv.WinServerObject.MPSEdition)"
        }
    }
}

# Script Setup. Checking Parameters
Write-Host

## Checing Verbose flag
if($verbose){$verbosepreference = "Continue"}else{$erroractionpreference = "SilentlyContinue"}

## Verifying that File/Server was passed. If not or -help I Call HelpMe and close.
if(!$file -and !$server -and !$all -or $help){HelpMe;Return}

## Verify Valid Edition was Passed
if(!$show -and ($PSVer -notmatch "STD|ADV|ENT|PLT"))
{
    Write-Host " PS Edition [$PSVER] is NOT Valid. Please use STD, ADV, ENT, or PLT" -fore RED
    Write-Host
    Return
}

# If $Server and we can ping it we run Set-PSVer against Server
if($server -and (ping-server $server))
{
    Set-PSVer $server
    Write-host
}

# Check for -File and Verify the file is valid
if($File -and (test-Path $file))
{
    Write-Verbose " – Processing File [$file]"
    # Process each Server checking for blanks
    foreach($Server in (get-Content $file | where{$_ -match "^\S"}))
    {
        Write-Host " + Processing Server [$Server]"
        if(ping-Server $server){Set-PSVer $Server}else{Write-Host "   – Ping Failed to [$Server]" -fore Red}
        Write-host
    }
}

if($all){Set-PSALL}

Write-Host

My Own Scripting Games!

I haven’t had time to participate in the 2008 and I have kinda missed out. But alas… a need has come through for me.

This is what I am looking for


I need to create a Test AD environment. I don’t need to mimic a specific production AD, but more like all of them 🙂
I want an AD environment that has plenty of user/computer accounts and plenty of Groups. I also want a large number of OUs for testing Group Policy Scripts.
These need to be realistic numbers so here is the challenge I came up with (note, Quest tools are allowed!)


# A TestOU OU
# A TestComputers OU
# A TestUsers OU
# A TestGroups OU
# 10K OU’s Under TestOU
## Each of the 10k OUs will have 4 Child OUs
### Each OU should have 5 users Accounts and 5 Machines Accounts
# Create 500 Group Policies.
# Link 100 policies on the 10k Base OUs
# Create 2000 Users in the TestUser OU
# Create 2000 Computers in the TestComputer OU
# Find all the Users
# Create 2K Groups
## Add Even Numbered Users to Even Groups
## Add Odd Numbered Users to Odd Groups


I will post my solution as soon as I am done, but I would like other people’s thoughts so POST away!

An Interactive Case for Powershell (Yet more Citrix Fun!)

I was recently in a discussion on Brian Madden Forums about listing Citrix Information and exporting to CSV. It seemed like a
perfect fit for Powershell so I converted the VBScripts to Powershell (of course taking an 85 line script to 3 lines. Convert is
hardly the correct term.)

Here is the Forum Topic
http://www.brianmadden.com/Forum/Topic/95285

Here is my Code. There were three scripts, so I made three as well.

# Apps by Server CTXApps_by_Server_w_Users
$MF = (New-Object -com MetaFrameCOM.MetaFrameFarm)
$MF.Initialize(1)
$MF.Servers | Select-Object ServerName -expand Applications | Select-Object ServerName,AppName,DistinguishedName,
       @{n=‘Users’;e={$_.Users | %{$_.UserName}}},
       @{n=‘Groups’;e={$_.Groups | %{$_.GroupName}}} | export-Csv C:\AppsByServer.Csv -noType
# Apps with Servers
$MF = New-Object -com MetaFrameCOM.MetaFrameFarm
$MF.Initialize(1)
$MF.Applications | Select-Object AppName,DistinguishedName,
      @{n="Servers";e={$_.Servers | foreach{$_.ServerName}}} | export-Csv C:\AppsWithServer.Csv -noType
# Apps with Servers and Users CTXApps_w_Servers_w_Users
$MF = New-Object -com MetaFrameCOM.MetaFrameFarm
$MF.Initialize(1)
$MF.Applications | Select-Object AppName,DistinguishedName,
      @{n="Servers";e={$_.Servers | foreach{$_.ServerName}}},
      @{n=‘Users’;e={$_.Users | %{$_.UserName}}},
      @{n=‘Groups’;e={$_.Groups | %{$_.GroupName}}} | export-Csv C:\AppsWithServerandUsers.Csv -noType

If you notice in my three scripts they all start with the same two lines. Effectively these are one liners that could be used
interactively. I think this does a great job of showing Citrix Admins how nicely Powershell will fit in to their daily lives.
Things that use to take 100s of lines of script writing can now be done interactively at a shell.

Secure String Followup

As a follow up on Converting Secure String discussed here: http://bsonposh.com/modules/wordpress/?p=66

Joel “Jaykul” Bennett released this script: http://www.powershellcentral.com/scripts/116

This is a slightly more secure way to store passwords in a text file for use later. Check it out.

Get-CitrixServerLoad (The power of objects in Citrix)

I watch the forums at BrianMadden.com because I use Powershell a lot for Citrix. This question was brought up.

Q: How could one:
– query “server load” on all servers part of the farm
– extract all server under a minimum server load
– apply an “Offline” load evaluator on the extracted servers (in order to make them unavailable on the farm)

I posted a script to do what they wanted, but then I got to thinking… while it did achieve the goal it wasn’t very Powershellish.

As I have said over and over. The glory of Powershell is the objects. So I decided to Post this entry showing what I would consider the Powershell way 🙂

Ideally you should just do this at the prompt

PS> Get-CitrixServers | where{$_.WinServerObject.Serverload -lt $load} | Set-CitrixLoadEvalutor “OffLine”

This is easy to achieve with the following scripts or even better make them functions!

Get-CitrixServers

param($Server)
$type = [System.Type]::GetTypeFromProgID("MetaframeCOM.MetaframeFarm",$Server)
$mfarm = [system.Activator]::CreateInstance($type)
$mfarm.Initialize(1)
$mfarm.zones | foreach-Object{$_.OnlineServers}

Set-CitrixLoadEvalutor

Param($server,$LoadEvaluator = "MFDefaultLE",[switch]$Verbose)
#NOTE: This only work for 4.0 and 4.5
if($verbose){$verbosepreference = "Continue"}

function Set-LE{
    Param($mySrv)
    # Getting Current LE
    write-Verbose "   + Set-LE called : $($mySrv.ServerName)"
    $le = $mfServer.AttachedLE
    $le.LoadData(1)
    Write-Verbose "     – Old Evaluator: $($le.LEName)"
    Write-Verbose "     – Setting to $LoadEvaluator"

    # Assigning New LE
    $mySrv.AttachLEByName($LoadEvaluator)

    # Checking LE
    $le = $mySrv.AttachedLE
    $le.LoadData(1)
    Write-Verbose "     – Load Evaluator Set to $($le.LEName)"

}

if($Server)
{
    # Loading Server Object
    Write-Verbose " + Processing $Server"
    $type = [System.Type]::GetTypeFromProgID("MetaframeCOM.MetaframeServer",$Server)
    $mfServer = [system.Activator]::CreateInstance($type)
    $mfServer.Initialize(6,$Server)
    Write-Verbose "   – Calling Set-LE"
    Set-LE $mfServer
}

if($list)
{
    foreach($Srv in (Get-Content $list))
    {
        Write-Verbose " + Processing $Srv"
        # Loading Server Object
        Write-Verbose "   – Getting Citrix Object"
        $type = [System.Type]::GetTypeFromProgID("MetaframeCOM.MetaframeServer",$Srv)
        $mfServer = [system.Activator]::CreateInstance($type)
        $mfServer.Initialize(6,$Srv)
        Write-Verbose "   – Calling Set-LE"
        Set-LE $mfServer
    }
}

if($input)
{
    foreach($Srv in $input)
    {
        Write-Verbose     " + Processing $Srv"
        if($Srv.ServerName)
        {
            Write-Verbose "   – Input is a Citrix Server: $Srv"
            Write-Verbose "   – Calling Set-LE"
            Set-LE $Srv
        }
        else
        {
            Write-Verbose "   – Input: $Srv"
            # Loading Server Object
            Write-Verbose "   – Getting Citrix Object"
            $type = [System.Type]::GetTypeFromProgID("MetaframeCOM.MetaframeServer",$Srv)
            $mfServer = [system.Activator]::CreateInstance($type)
            $mfServer.Initialize(6,$Srv)
            Write-Verbose "   – Calling Set-LE"
            Set-LE $mfServer
        }
    }
}

This was the all in one that I posted

Param($Server,$minLoad = 1000,$LoadEval,[switch]$verbose)
if($verbose){$verbosepreference = "continue"}
function Get-CitrixFarm{
    param($Srv)
    $type = [System.Type]::GetTypeFromProgID("MetaframeCOM.MetaframeFarm",$Srv)
    $mfarm = [system.Activator]::CreateInstance($type)
    $mfarm.Initialize(1)
    Write-Verbose "Loading Farm $($mFarm.FarmName)"
    return $mFarm
}
function Set-CitrixLoadEvalutor{
    Param($server,$LoadEvaluator = "MFDefaultLE")

    # Loading Server Object
    $type = [System.Type]::GetTypeFromProgID("MetaframeCOM.MetaframeServer",$Server)
    $mfServer = [system.Activator]::CreateInstance($type)
    $mfServer.Initialize(6,$Server)

    # Getting Current LE
    $le = $mfServer.AttachedLE
    $le.LoadData(1)
    Write-Verbose "Old Evaluator: $($le.LEName)"
    Write-Verbose "Setting Load Evaluator on $server to $LoadEvaluator"

    # Assigning New LE
    $mfServer.AttachLEByName($LoadEvaluator)

    # Checking LE
    $le = $mfServer.AttachedLE
    $le.LoadData(1)
    Write-Verbose "Load Evaluator Set to $($le.LEName)"
}

$farm = Get-CitrixFarm $Server
foreach($ctxServer in $farm.Servers)
{
    $load = $ctxServer.WinServerObject.Serverload
    Write-Host ("{0,-15} :: {1}" -f $ctxServer.ServerName,$load)
    if($load -lt $minLoad)
    {
        Write-Verbose "Setting Offline Load Eval"
        if($LoadEval){Set-CitrixLoadEvalutor $ctxServer.ServerName $LoadEval}
    }
}

« Prev - Next »