Posts RSS Comments RSS 253 Posts and 411 Comments till now

Functions and why they are important!

I often get asked why I use functions and when I think they should be used. Below is a guide I wrote explaining my theory on functions.


Why use functions?

Functions are, generally speaking, small single task tools (like a flathead screwdriver or hammer.) They do one thing and they do that one thing reliably well. If you take this approach when writing code you will find it easier to debug and find yourself writing less code. Why less code? Because, you’ll find you are now able to port your functions from one script to another or possibly even in your day to day life.

How you decide to write a function?

I have three basic guidelines for when to write a function.

First, if I find I am repeating the same code block over and over (like a code block that checks several services on a computer.) It makes sense to just write a function to perform the check and then run that against each server. This allows me to trouble-shoot the code more efficiently.

Second, if I find that I can use this code in other scripts. For example if I write a nice recursive parsing block. I may want to reuse that logic in another script.

Finally, if I determine the code is useful outside of this script. This may seem like the previous guideline but it is slightly different. A good example here would be a ping-server function. Not only is this useful in other scripts, but it is also useful in my day to day life.

Do you always design functions with the idea of reuse in mind?

Generally speaking it is a good idea to ALWAYS consider reuse when writing code. This is paramount when working with functions. The sole purpose of functions in life is for reuse.  This is major consideration when designing your functions. Consider how and where they will be used. This will help establish what parameters it should have and what (if any) defaults it should have.

What do you put in your functions?

Ideally, because we design code for reuse, it is best to be as verbose as possible.  Basic rule of thumb is hardcode nothing, all data should be passed by parameters. Certainly you can have default values for the parameters, but allow the function caller to specific other options without having to modify the function.  This comes back to the black box approach. One needs to consider the effect of every change the original function and how that will affect the script as a whole.

In v1 I always try to implement –verbose and –whatif with my own switches. In v2 this is handled for you.

Do you separate the logic from the function?

When designing functions one should think about the looping and processing logic. Generally you will find this is script specific and should be implemented outside of the function. Ideally you would want to restrict logic to the party that requires the logic. For example, if you have logic to process servers, keep that logic outside of the functions. There is no need to implement that logic over and over for each function call. On the other hand, if you have logic that is expressly the domain of the function do not go crazy trying to rip it out just to put in the calling script.

What makes a good function?

Great functions are born out of need, but grow out of use. As you grow in your understanding your functions will grow with you. They are like the friend that is always there when you need them, but like that friend they need attention and care. Below are some features that functions should have:

Well defined parameters:

You function needs to be very clear on what data it expects. You accomplish this by having very specific parameters (this often will include the data type as well.) If you absolutely must have a specific value to process make sure that is clear from the function. A great way to accomplish this is by assigning the parameters default value to (Throw ‘$ThisParam is required’).

Consistent and expected output:

This is absolutely critical. You do not want to guess at what data will come from the function. You want the data to be what is expected. Design the function so that it returns one or more of a single data type (i.e. string, DateTime, or Boolean.) Be very cautious not to pollute the data stream with unexpected data written with write-output or data that wasn’t captured in a variable.

Self contained:

The function should NOT rely on any variables from the script. If the function needs input from outside make it a parameter.

Portability:

The single most important job of a function is to be portable. If you do not plan to reuse the code you might as well write the code inline. A key factor to portability is to make sure your variable names will not collide with the calling script. A good rule of thumb is to preface them with $my or $func (like $MyServer or $FuncServer.)

 

Getting users group membership (tokengroups)

Around the same time as I wrote my Linked-Value Attribute script I also came up with this little gem. It also uses a constructed attribute provided with Windows 2003 called “tokengroups.” (Did I mention this includes recursive groups?)

Effectively it gets the attribute which returns an array of SIDs (in byte array form) for each group. I then use a function I posted about eariler called ConvertTo-Name to convert that BYTE array into a friendly group name we humans like.

Parameters:

  • – Account: Can be User samAccountName or DN of the user
  • – Verbose: Enables verbose output

More Info:
You may notice the GetInfoEx call I make on the user object. This is because tokengroups is not an actual attribute and does not get “populated” until you specifically request it. The GetInfoEx does exactly that.

Links:
MSDN: GetInfoEx
MSDN: tokenGroups

Get-TokenGroups.ps1

Param($Account,[switch]$Verbose)

if($verbose){$verbosepreference="Continue"}

Write-Host
Write-Verbose " – Account: $Account"
Write-Verbose " – Verbose: $Verbose"

function ConvertTo-Name($sid,[switch]$FromByte) {
    if($FromByte)
    {
        $ID = New-Object System.Security.Principal.SecurityIdentifier($sid,0)
    }
    else
    {
        $ID = New-Object System.Security.Principal.SecurityIdentifier($sid)
    }
    if($ID)
    {
        $User = $ID.Translate([System.Security.Principal.NTAccount])
        $User.Value
    }
}
function GetDNfromName{
    Param($name)
    $root = [ADSI]""
    $filter = "(sAMAccountName=$name)"
    $props = @("distinguishedName")
    $Searcher = new-Object System.DirectoryServices.DirectorySearcher($root,$filter,$props)
    $Searcher.FindOne().properties.distinguishedname
}

if($Account -notmatch "CN=(.*),((OU|DC)=\w*)*")
{
    Write-Verbose " + Getting User DN for [$Account]"
    $Account = GetDNfromName $Account
    Write-Verbose "   – GetDNfromName returned [$Account]"
}

Write-Verbose " – Getting User Object"
$UserAccount = [ADSI]"LDAP://$Account"

Write-Verbose " – Calling GetInfoEx"
$UserAccount.GetInfoEx(@("tokengroups"),0)

Write-Verbose " – Getting tokengroups"
$groups = $UserAccount.Get("tokengroups")

Write-Verbose " + Processing Groups"
foreach($group in $groups)
{
    $GroupName = ConvertTo-Name $group -FromByte
    Write-Verbose "   – Found group [$GroupName]"
    $GroupName
}

Write-Host

Functions for Active Directory Permissions

Below are some of the function I have written to work with Access Control List in Active Directory.

Here is the list and what they do
Get-ADACL: Gets the Access Control List of an AD Object
Set-ADACL: Set the Access Control List of an AD Object
New-ADACE: Creates a Access Control Entry to add to an AD Object
ConvertTo-Sid: Converts a UserName to a SID
ConvertTo-Name: Converts a SID to the UserName
Get-Self: Gets current Security Identity


function Get-ADACL {
    Param($DNPath,[switch]$SDDL,[switch]$help,[switch]$verbose)
    function HelpMe{
        Write-Host
        Write-Host " Get-ADACL.ps1:" -fore Green
        Write-Host "   Gets ACL object or SDDL for AD Object"
        Write-Host
        Write-Host " Parameters:" -fore Green
        Write-Host "   -DNPath                : Parameter: DN of Object"
        Write-Host "   -sddl                  : [SWITCH]:  Output SDDL instead of ACL Object"
        Write-Host "   -Verbose               : [SWITCH]:  Enables Verbose Output"
        Write-Host "   -Help                  : [SWITCH]:  Displays This"
        Write-Host
        Write-Host " Examples:" -fore Green
        Write-Host "   Get ACL for ‘cn=users,dc=corp,dc=lab’" -fore White
        Write-Host "     .\Get-ADACL.ps1 ‘cn=users,dc=corp,dc=lab’" -fore Yellow
        Write-Host "   Get SDDL for ‘cn=users,dc=corp,dc=lab’" -fore White
        Write-Host "     .\Get-ADACL.ps1 ‘cn=users,dc=corp,dc=lab’ -sddl " -fore Yellow
        Write-Host
    }
   
    if(!$DNPath -or $help){HelpMe;return}
   
    Write-Host
    if($verbose){$verbosepreference="continue"}
   
    Write-Verbose " + Processing Object [$DNPath]"
    $DE = [ADSI]"LDAP://$DNPath"
   
    Write-Verbose "   – Getting ACL"
    $acl = $DE.psbase.ObjectSecurity
    if($SDDL)
    {
        Write-Verbose "   – Returning SDDL"
        $acl.GetSecurityDescriptorSddlForm([System.Security.AccessControl.AccessControlSections]::All)
    }
    else
    {
        Write-Verbose "   – Returning ACL Object [System.DirectoryServices.ActiveDirectoryAccessRule]"
        $acl.GetAccessRules($true,$true,[System.Security.Principal.SecurityIdentifier])
    }
}
function Set-ADACL {
    Param($DNPath,$acl,$sddl,[switch]$verbose,[switch]$help)
    function HelpMe{
        Write-Host
        Write-Host " Set-ADACL.ps1:" -fore Green
        Write-Host "   Sets the AD Object ACL to ‘ACL Object’ or ‘SDDL’ String"
        Write-Host
        Write-Host " Parameters:" -fore Green
        Write-Host "   -DNPath                : Parameter: DN of Object"
        Write-Host "   -ACL                   : Parameter: ACL Object"
        Write-Host "   -sddl                  : Parameter: SDDL String"
        Write-Host "   -Verbose               : [SWITCH]:  Enables Verbose Output"
        Write-Host "   -Help                  : [SWITCH]:  Displays This"
        Write-Host
        Write-Host " Examples:" -fore Green
        Write-Host "   Set ACL on ‘cn=users,dc=corp,dc=lab’ using ACL Object" -fore White
        Write-Host "     .\Set-ADACL.ps1 ‘cn=users,dc=corp,dc=lab’ -ACL $acl" -fore Yellow
        Write-Host "   Set ACL on ‘cn=users,dc=corp,dc=lab’ using SDDL" -fore White
        Write-Host "     .\Set-ADACL.ps1 ‘cn=users,dc=corp,dc=lab’ -sddl `$mysddl" -fore Yellow
        Write-Host
    }
   
    if(!$DNPath -or (!$acl -and !$sddl) -or $help){HelpMe;Return}
   
    Write-Host
    if($verbose){$verbosepreference="continue"}
    Write-Verbose " + Processing Object [$DNPath]"
   
    $DE = [ADSI]"LDAP://$DNPath"
    if($sddl)
    {
        Write-Verbose "   – Setting ACL using SDDL [$sddl]"
        $DE.psbase.ObjectSecurity.SetSecurityDescriptorSddlForm($sddl)
    }
    else
    {
        foreach($ace in $acl)
        {
            Write-Verbose "   – Adding Permission [$($ace.ActiveDirectoryRights)] to [$($ace.IdentityReference)]"
            $DE.psbase.ObjectSecurity.SetAccessRule($ace)
        }
    }
    $DE.psbase.commitchanges()
    Write-Host
}
function New-ADACE {
    Param([System.Security.Principal.IdentityReference]$identity,
        [System.DirectoryServices.ActiveDirectoryRights]$adRights,
        [System.Security.AccessControl.AccessControlType]$type,
        $Guid)
   
    $help = @"
    $identity
        System.Security.Principal.IdentityReference
        http://msdn.microsoft.com/en-us/library/system.security.principal.ntaccount.aspx
   
    $adRights
        System.DirectoryServices.ActiveDirectoryRights
        http://msdn.microsoft.com/en-us/library/system.directoryservices.activedirectoryrights.aspx
   
    $type
        System.Security.AccessControl.AccessControlType
        http://msdn.microsoft.com/en-us/library/w4ds5h86(VS.80).aspx
   
    $Guid
        Object Type of the property
        The schema GUID of the object to which the access rule applies.
"
@
    $ACE = New-Object System.DirectoryServices.ActiveDirectoryAccessRule($identity,$adRights,$type,$guid)
    $ACE
}
function ConvertTo-Sid($UserName,$domain = $env:Computername) {
   $ID = New-Object System.Security.Principal.NTAccount($domain,$UserName)
   $SID = $ID.Translate([System.Security.Principal.SecurityIdentifier])
   $SID.Value
}
function ConvertTo-Name($sid) {
   $ID = New-Object System.Security.Principal.SecurityIdentifier($sid)
   $User = $ID.Translate( [System.Security.Principal.NTAccount])
   $User.Value
}
function Get-Self{
   ([Security.Principal.WindowsIdentity]::GetCurrent())
}

Bulk Imports using CSV (cross post from TurboChargeAD.org)

As some of you may know I am doing some guest blogging over at TurboChargeAD.org and below is a link to the second such article.

Here is the Link: Bulking Importing User from CSV file using Quest cmdlets

Here is the code

#Import-ADUser.ps1
#requires -pssnapin Quest.ActiveRoles.ADManagement
Param($file,$OU,[switch]$whatif,[switch]$verbose)

if($verbose){$VerbosePreference = "Continue"}

if(!(test-path $file))
{
   throw " File NOT found!"
   return
}

if(!$ou)
{
    # Setting the OU to the Users container
    $ou = "CN=Users,{0}" -f ([ADSI]"").distinguishedName[0]
}

Write-Verbose " Using File: $file"
Write-Verbose " Using OU: $ou"

foreach($user in (Import-Csv $file))
{
    $props = @{}
    # Getting a list property names.
    $propNames = $user | Get-Member -MemberType properties | %{$_.name}
    # Foreach of the property names add an entry in the hash table with
    # the key being the property name and value being the value from the object
    foreach($prop in $propNames)
    {
        # This removes quotes if they exist
    $value = $user.$prop -replace "’|`"",""
        $props += @{$prop=$value}
    }
    # Create User using the displayname as the CN
    $MyUser = new-qaduser -name $user.displayName `
                          -ObjectAttributes $props `
                          -parent $OU `
                          -whatif:$whatif `
                          -verbose:$verbose
    $MyUser
}

Test-Host (WMI Ping -or Port Check)

I often find (specifically when using WMI) the need to ping the machine first before performing any queries. WMI takes FOREVER to timeout. I decided that I should use a script/function to test a host before passing it down the pipe.

There are a couple of problems with this approach that I had to consider.
1) How do I know what to test without corrupting the pipe or using foreach?
– For this I added a “-property” parameter that would allow the user to pick what property to check against, but still output the entire object that was inputted.

2) What about firewalls that block ping?
– Added TestPort function that does a TCP connect and returns $true or $false

3) What if I want a conditional port check.
– Added the ability to Change the Default Port for TCP Connection Test

Here is the script that I came up with and some of things it does.
Parameters
$property: The Property to Ping or Test (Default is none.)
$tport: The Port to test against (Default is 135. Used with -port)
$timeout: The timeout for the connection (Default is 1000 ms, Used with -port)
[switch]$port: Switch to Test a port instead of Ping
[switch]$verbose: Provides Verbose Output.
Features
– Will Ping ‘$_’ by default
– Can pass the property that contains the Host to test using -property
– Can use -port to test a port instead of ping. (Uses TCP)
– Maintains the Object that is tested and passes it down the pipe if connection is passed.

A Demo of the script in action

Best Viewed Full Screen

Get the Flash Player to see this content.


Script CODE

Param($property,$tport=135,$timeout=1000,[switch]$port,[switch]$verbose)
Begin{
    function TestPort {
        Param($srv)
        $error.Clear()
        $ErrorActionPreference = "SilentlyContinue"
        $tcpclient = new-Object system.Net.Sockets.TcpClient
        $iar = $tcpclient.BeginConnect($srv,$tport,$null,$null)
        $wait = $iar.AsyncWaitHandle.WaitOne($timeout,$false)
        # Traps    
        trap {if($verbose){Write-Host "General Exception"};return $false}
        trap [System.Net.Sockets.SocketException]
        {
            if($verbose){Write-Host "Exception: $($_.exception.message)"}
            return $false
        }
        if(!$wait)
        {
            $tcpclient.Close()
            if($verbose){Write-Host "Connection Timeout"}
            return $false
        }
        else
        {
            $tcpclient.EndConnect($iar) | out-Null
            $tcpclient.Close()
        }
        if(!$error[0]){return $true}
    }
    function PingServer {
        Param($MyHost)
        $pingresult = Get-WmiObject win32_pingstatus -f "address=’$MyHost’"
        if($pingresult.statuscode -eq 0) {$true} else {$false}
    }
}
Process{
    if($_)
    {
        if($port)
        {
            if($property)
            {
                if(TestPort $_.$property){$_}  
            }
            else
            {
                if(TestPort $_){$_}
            }
        }
        else
        {
            if($property)
            {
                if(PingServer $_.$property){$_}  
            }
            else
            {
                if(PingServer $_){$_}
            }
        }
    }
}

If you want this as a function use this code

function Test-Host{
Param($property,$tport=135,$timeout=1000,[switch]$port,[switch]$verbose)
Begin{
    function TestPort {
        Param($srv)
        $error.Clear()
        $ErrorActionPreference = "SilentlyContinue"
        $tcpclient = new-Object system.Net.Sockets.TcpClient
        $iar = $tcpclient.BeginConnect($srv,$tport,$null,$null)
        $wait = $iar.AsyncWaitHandle.WaitOne($timeout,$false)
        # Traps    
        trap {if($verbose){Write-Host "General Exception"};return $false}
        trap [System.Net.Sockets.SocketException]
        {
            if($verbose){Write-Host "Exception: $($_.exception.message)"}
            return $false
        }
        if(!$wait)
        {
            $tcpclient.Close()
            if($verbose){Write-Host "Connection Timeout"}
            return $false
        }
        else
        {
            $tcpclient.EndConnect($iar) | out-Null
            $tcpclient.Close()
        }
        if(!$error[0]){return $true}
    }
    function PingServer {
        Param($MyHost)
        $pingresult = Get-WmiObject win32_pingstatus -f "address=’$MyHost’"
        if($pingresult.statuscode -eq 0) {$true} else {$false}
    }
}
Process{
    if($_)
    {
        if($port)
        {
            if($property)
            {
                if(TestPort $_.$property){$_}  
            }
            else
            {
                if(TestPort $_){$_}
            }
        }
        else
        {
            if($property)
            {
                if(PingServer $_.$property){$_}  
            }
            else
            {
                if(PingServer $_){$_}
            }
        }
    }
}}

Get/Set-ADACL (ACL and SDDLs for Active Directory!)

A friend had a need to get/set Active Directory ACLs. So I wrote these.

They will use [System.DirectoryServices.ActiveDirectoryAccessRule] objects or SDDLs strings.

Note: I put the .NET classes and MS Spec for SDDLs at the bottom. Dont miss it!

Get-ADACL.ps1

# Get-ADACL.ps1
Param($DNPath,[switch]$SDDL,[switch]$help,[switch]$verbose)
function HelpMe{
    Write-Host
    Write-Host " Get-ADACL.ps1:" -fore Green
    Write-Host "   Gets ACL object or SDDL for AD Object"
    Write-Host
    Write-Host " Parameters:" -fore Green
    Write-Host "   -DNPath                : Parameter: DN of Object"
    Write-Host "   -sddl                  : [SWITCH]:  Output SDDL instead of ACL Object"
    Write-Host "   -Verbose               : [SWITCH]:  Enables Verbose Output"
    Write-Host "   -Help                  : [SWITCH]:  Displays This"
    Write-Host
    Write-Host " Examples:" -fore Green
    Write-Host "   Get ACL for ‘cn=users,dc=corp,dc=lab’" -fore White
    Write-Host "     .\Get-ADACL.ps1 ‘cn=users,dc=corp,dc=lab’" -fore Yellow
    Write-Host "   Get SDDL for ‘cn=users,dc=corp,dc=lab’" -fore White
    Write-Host "     .\Get-ADACL.ps1 ‘cn=users,dc=corp,dc=lab’ -sddl " -fore Yellow
    Write-Host
}

if(!$DNPath -or $help){HelpMe;return}

Write-Host
if($verbose){$verbosepreference="continue"}

Write-Verbose " + Processing Object [$DNPath]"
$DE = [ADSI]"LDAP://$DNPath"

Write-Verbose "   – Getting ACL"
$acl = $DE.psbase.ObjectSecurity
if($SDDL)
{
    Write-Verbose "   – Returning SDDL"
    $acl.GetSecurityDescriptorSddlForm([System.Security.AccessControl.AccessControlSections]::All)
}
else
{
    Write-Verbose "   – Returning ACL Object [System.DirectoryServices.ActiveDirectoryAccessRule]"
    $acl.GetAccessRules($true,$true,[System.Security.Principal.SecurityIdentifier])
}

Set-ADACL.ps1

# Set-ADACL.ps1
Param($DNPath,$acl,$sddl,[switch]$verbose,[switch]$help)
function HelpMe{
    Write-Host
    Write-Host " Set-ADACL.ps1:" -fore Green
    Write-Host "   Sets the AD Object ACL to ‘ACL Object’ or ‘SDDL’ String"
    Write-Host
    Write-Host " Parameters:" -fore Green
    Write-Host "   -DNPath                : Parameter: DN of Object"
    Write-Host "   -ACL                   : Parameter: ACL Object"
    Write-Host "   -sddl                  : Parameter: SDDL String"
    Write-Host "   -Verbose               : [SWITCH]:  Enables Verbose Output"
    Write-Host "   -Help                  : [SWITCH]:  Displays This"
    Write-Host
    Write-Host " Examples:" -fore Green
    Write-Host "   Set ACL on ‘cn=users,dc=corp,dc=lab’ using ACL Object" -fore White
    Write-Host "     .\Set-ADACL.ps1 ‘cn=users,dc=corp,dc=lab’ -ACL $acl" -fore Yellow
    Write-Host "   Set ACL on ‘cn=users,dc=corp,dc=lab’ using SDDL" -fore White
    Write-Host "     .\Set-ADACL.ps1 ‘cn=users,dc=corp,dc=lab’ -sddl `$mysddl" -fore Yellow
    Write-Host
}

if(!$DNPath -or (!$acl -and !$sddl) -or $help){HelpMe;Return}

Write-Host
if($verbose){$verbosepreference="continue"}
Write-Verbose " + Processing Object [$DNPath]"

$DE = [ADSI]"LDAP://$DNPath"
if($sddl)
{
    Write-Verbose "   – Setting ACL using SDDL [$sddl]"
    $DE.psbase.ObjectSecurity.SetSecurityDescriptorSddlForm($sddl)
}
else
{
    foreach($ace in $acl)
    {
        Write-Verbose "   – Adding Permission [$($ace.ActiveDirectoryRights)] to [$($ace.IdentityReference)]"
        $DE.psbase.ObjectSecurity.SetAccessRule($ace)
    }
}
$DE.psbase.commitchanges()
Write-Host

More Info
I used the following .NET Classes
System.DirectoryServices.DirectoryEntry
http://msdn2.microsoft.com/en-us/library/system.directoryservices.directoryentry.aspx
System.DirectoryServices.ActiveDirectoryAccessRule
http://msdn2.microsoft.com/en-us/library/system.directoryservices.activedirectoryaccessrule.aspx
System.DirectoryServices.ActiveDirectorySecurity
http://msdn2.microsoft.com/en-us/library/system.directoryservices.activedirectorysecurity.aspx
System.Security.AccessControl.AccessControlSections
http://msdn2.microsoft.com/en-us/library/system.security.accesscontrol.accesscontrolsections(vs.80).aspx

SDDL Info
MS: http://msdn2.microsoft.com/en-us/library/aa379567.aspx

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}
    }
}

Get-CitrixHotfix: The Bitter/Sweet of Write-Verbose

There are a whole host of of Write-* Cmdlets.

Write-Debug
Write-Error
Write-Host
Write-Output
Write-Progress
Write-Verbose
Write-Warning

Each one of these are useful, but I want to specifically talk about Write-Verbose. Note, Write-Debug work basically the same. Also, for those of you that follow the Powershell Podcast over at http://powerscripting.wordpress.com some of this was covered already in Episode 11.

The way Write-Verbose works is that it uses $VerbosePreference to determine what to do. This is very useful because it gives you the ability to easily control if the string is written to the host. I think this is a good time to clarify that write-verbose only writes to the host and does NOT pollute the output stream which is SUPER useful.

Lets point out the goods and bads

Goods
1) Can use a switch Parameter to easily control the console info (I normally use $verbose)
2) It writes to host so you dont pollute the object output
3) Nice for writing Data to the console on scripts that take a long time to run

Bads
1) It has a header on every line that cannot be removed “VERBOSE:”
2) It is Yellow (eek!)

The good news is you can control the color using $host.PrivateData
More Info http://ps1.soapyfrog.com/2007/01/29/debug-and-verbose-colouring/

The following is a script that I wrote that collects Citrix Hotfixes. I have a large number of servers so I wanted to be able see where I was and I also wanted a overview of the hotfixes, but I also wanted to output and object for filtering purposes. In this case Write-Verbose was perfect. I was able to write to the screen (host) what I wanted to see without changing the object output. I was also able to control whether it was outputed by using a switch parameter to control $VerbosePreference.

Param($Server,$log,[switch]$Farm,[switch]$Verbose,[switch]$debug)
# Get-CitrixHotfix.ps1

if($Verbose){$VerbosePreference = "Continue"}
if($debug){Set-PSDebug -Step}

Write-Host "`nProcessing…`n"

if($Farm)
{
    # Get Farm Object
    $type = [System.Type]::GetTypeFromProgID("MetaframeCOM.MetaframeFarm",$Server)
    $CTXFarm = [system.Activator]::CreateInstance($type)
    $CTXFarm.Initialize(1)

    # Creating Collection for Custom Objects
    $myCol = @()

    foreach($Srv in $CTXFarm.Servers)
    {
        # Create Custom Object
        $myobj = "" | Select-Object Name,Hotfix
        $myobj.Name = $Srv.ServerName
        $myobj.Hotfix = @()

        Write-Verbose $Srv.ServerName

        # Get Hotfix Information for the Server and add to Custom Object
        $CTXServer = $CTXFarm.GetServer2(6,$Srv.ServerName)
        $CTXServer.winServerObject2.hotfixes | %{Write-Verbose " – $($_.Name)";$myobj.HotFix += $_.name}

        # Add Server Object to Collection
        $myCol += $myobj
    }
    # Output Collection
    if($log)
    {
        @(foreach($obj in $mycol)
        {
            Write-Output $obj.Name
            foreach($hf in $obj.Hotfix){write-Output " – $hf"}
        }) | out-File $log -enc ASCII
    }
    else
    {
        $mycol
    }
}
else
{
    $myobj = "" | Select-Object Name,HotFix
    $myobj.Name = $Server
    $myobj.HotFix = @()
    $type = [System.Type]::GetTypeFromProgID("MetaframeCOM.MetaframeServer",$Server)
    $mfServer = [system.Activator]::CreateInstance($type)
    $mfServer.Initialize(6,$Server)
    Write-Output $Server
    $mfServer.winServerObject2.hotfixes | foreach-Object{$myobj.HotFix += $_.Name}
    $myobj
}

write-host

if($debug){Set-PSDebug -off}