Posts RSS Comments RSS 127 Posts and 199 Comments till now

Archive for the 'functions' Category

Format-XML (Journey to Pretty XML output)

I ran across a need to use Export-CliXML to produce human readable XML. Since, I never really cared what the output looked like so I let the blob go.

Now I care :)
I sent a message to the Powershell Dev Team and as always they are super speedy in response. (less than 1hr… that is INSANE.)

Enter Format-XML
http://blogs.msdn.com/powershell/archive/2008/01/18/format-xml.aspx

Help for Export-Clixml
http://technet.microsoft.com/en-us/library/bb978636.aspx

Thanks Again Jeffrey and Team!

p.s. I dont believe I ever said the output (Export-CliXML) looked like crap… just not Pretty :)

Using Switch -RegEx to create Custom Object (Getting HBA Info)

The other day I had a need to collect HBA (Host Base Fiber Adapters) from all my Servers. So the first place I looked was at WMI, but unfortunately… no dice. It didnt have the information I needed. The only way I knew to get the info I needed was to use was HBACmd.exe (utility to collect HBA information remotely.) So I went to writing a wrapper script in powershell to call the exe and then grep the text for what I was looking for, but I thought… HEY! Thats not the powershell thing to do! We do objects not text. So I went to parsing the text and making an object out of it. The script below is the result and while I dont believe many of you will find it particularly useful as it has a VERY specific use, I wanted to share with you how I went about objectizing the output.

Here is a littel Q and A on the script

Q: Purpose?
A: To get all the HBA information include Type, Firmware, Bios, Target Lun, WWN and a slew of other stuff.

Q: Why Did I objectize my text?
A: Because I know can simply use properties to filter and output data. Like what Machines have what Bios and what type of HBAs they have. Before I would have the parse the text for every different senario… now I just use where-object and filter away.

I few things that I wanted to point out here are the use of Switch to create the Custom Objects. IMO, Switch is one of the most powerful commands in the Powershell Language. It is INSANELY Powerfull. To be honest, It is pretty much the only one you need.

To compare it “Select Case” in vbscript would be insulting, but it can peform a similar function Like

switch ($a){
     Value1    {"It was Value 1"}
     Value2    {"It was Value 2"}
     Value3    {"It was Value 3"}
     Value4    {"It was Value 4"}
}

It would take a whole series of post to completely cover switch, but for this one I only want to go over -regex use. For complete use read the help located:
PS> Get-help About_Switch # Read it, Learn it, Love it

Some Quick Notes about Switch
- Can use RegEx, WildCard, Exact, CaseSensitive, or File options.
- It takes input via Pipeline {expression} or File. The cool thing is the pipeline can be any expression that results in piped output.
- For each match it can perform any ScritpBlock use $_ as the current Item
- It performs EVERY match on each element unless you use Continue after a match to stop processing that record

Like I said, Switch is insanely powerful. Just one of those powers is using RegEx for comparison.

Here is an example of using the -RegEx option

switch -RegEx (Get-ChildItem C:\test)
{
    "^\d"                  {"Starts with number:          " + $_.FullName}
    "\d"                   {"Has a number in it:          " + $_.FullName}
    "[^A-Za-z]"            {"Does NOT start with Number:  " + $_.FullName}
    "tmp"                  {"Has ‘tmp’ in it:             " + $_.FullName}
    # Notice that you can even use and expression to match
    {$_.Mode -match "-a"}  {"Has Archive Bit Set:         " + $_.FullName}
}

Now.. lets look at the script below. You will noticed I used RegEx to decide what value gets put in to what property of the object.

The script converts the output of three commands into two different objects. Lets look at one of them

It takes text like This

Manageable HBA List

Port WWN   : 10:00:00:00:11:11:11:11
Node WWN   : 20:00:00:00:11:11:11:11
Fabric Name: 00:00:00:00:00:00:00:00
Flags      : 0000f0a5
Host Name  : Server1
Mfg        : Emulex Corporation

Port WWN   : 10:00:00:00:22:22:22:22
Node WWN   : 20:00:00:00:22:22:22:22
Fabric Name: 00:00:00:00:00:00:00:00
Flags      : 0000f0a5
Host Name  : Server1
Mfg        : Emulex Corporation

And converts Into an object like This

  TypeName: System.Management.Automation.PSCustomObject

Name        MemberType   Definition
—-        ———-   ———-
Equals      Method       System.Boolean Equals(Object obj)
GetHashCode Method       System.Int32 GetHashCode()
GetType     Method       System.Type GetType()
ToString    Method       System.String ToString()
Fabric      NoteProperty System.String Fabric=00:00:00:00:00:00:00:00
Flags       NoteProperty System.String Flags=0000f0a5
HBADetail   NoteProperty System.Object[] HBADetail=System.Object[]
Host        NoteProperty System.String Host=Server1
LUN         NoteProperty System.String LUN=01
MFG         NoteProperty System.String MFG=Emulex Corporation
NodeWWN     NoteProperty System.String NodeWWN=20:00:00:00:11:11:11:11
PortWWN     NoteProperty System.String PortWWN=10:00:00:00:11:11:11:11

Here is the Script

Param($List,$HostName,[switch]$FullDetail,[switch]$Verbose)
Begin{
    $erroractionpreference = "SilentlyContinue"
    $HBACMDPath = "<path To HbaCmd.exe>"
    function CreateHBAListObj{
        Param($srv)
        $objCol = @()
        $result = &"$HBACMDPath" "h=$srv" ListHBAs
        foreach($item in $result)
        {
            $parsd = $item.split([string[]](": "),[system.StringSplitOptions]::RemoveEmptyEntries)
            switch -regex ($parsd[0])
            {
                "^Port" {
                            $myobj = "" | Select-Object Host,PortWWN,NodeWWN,Fabric,Flags,LUN,MFG
                            $myobj.PortWWN = $parsd[1]
                            $myobj.Lun = GetTargetLun $srv $myobj.PortWWN
                        }
                "^Node" {$myobj.NodeWWN = $parsd[1]}
                "^Fabr" {$myobj.Fabric  = $parsd[1]}
                "^Flag" {$myobj.Flags   = $parsd[1]}
                "^Host" {$myobj.Host    = $parsd[1]}
                "^MFG " {
                            $myobj.MFG     = $parsd[1]
                            $objCol += $myObj
                        }
            }
        }
        $objCol
    }
    function CreateHBAInfoObj{
        Param($srv,$wwn)
        $objCol = @()
        $result = &"$HBACMDPath" "h=$srv" HBAAttrib $wwn

        $myobj = "" |Select-Object Host,MFG,SN,Model,ModelDesc,NodeWWN,NodeSymname,
                                   HWVersion,ROM,FW,VenderID,Ports,DriverName,DeviceID,HBAType,
                                   OpFW,SLT1FW,SLT2FW,IEEEAddress,BootBios,DriverVer,KernelVer
        foreach($item in $result)
        {
            $parsd = $item.split([string[]](": "),[system.StringSplitOptions]::RemoveEmptyEntries)
            switch -regex ($parsd[0])
            {
                "^Host"         {$myobj.Host         = $parsd[1]}
                "^Manufacturer" {$myobj.MFG          = $parsd[1]}
                "^Serial"       {$myobj.Sn           = $parsd[1]}
                "^Model  "      {$myobj.Model        = $parsd[1]}
                "^Model Desc"   {$myobj.ModelDesc    = $parsd[1]}
                "^Node WWN "    {$myobj.NodeWWN      = $parsd[1]}
                "^Node Symname" {$myobj.NodeSymname  = $parsd[1]}
                "^HW"           {$myobj.HWVersion    = $parsd[1]}
                "^Opt"          {$myobj.ROM          = $parsd[1]}
                "^FW"           {$myobj.FW           = $parsd[1]}
                "^Vender"       {$myobj.VenderID     = $parsd[1]}
                "^Number"       {$myobj.Ports        = $parsd[1]}
                "^Driver Name"  {$myobj.DriverName   = $parsd[1]}
                "^Device"       {$myobj.DeviceID     = $parsd[1]}
                "^HBA Type"     {$myobj.HBAType      = $parsd[1]}
                "^Operational"  {$myobj.OpFW         = $parsd[1]}
                "^SLI1 FW"      {$myobj.SLT1FW       = $parsd[1]}
                "^SLI2 FW"      {$myobj.SLT2FW       = $parsd[1]}
                "^IEEE"         {$myobj.IEEEAddress  = $parsd[1]}
                "^Boot "        {$myobj.BootBios     = $parsd[1]}
                "^Driver Ver"   {$myobj.DriverVer    = $parsd[1]}
                "^Kernel "      {$myobj.KernelVer    = $parsd[1]
                                 $objCol += $myObj}
            }
        }
        $objCol
    }
    function GetTargetLun{
        Param($srv,$wwn)
        $objCol = @()
        $result = &"$HBACMDPath" "h=$srv" TargetMapping $wwn
        [int]$lun = 0
        switch -regex ($result)
        {
            "^SCSI OS Lun" {$lun = $_.split([string[]](": "),[system.StringSplitOptions]::RemoveEmptyEntries)[1].trim()}
        }
        "{0:x}" -f $lun
    }
    function Ping-Server {
        Param([string]$srv)
        if($srv -eq ""){return $false}
        $pingresult = Get-WmiObject win32_pingstatus -f "address=’$srv’"
        if($pingresult.statuscode -eq 0) {$true} else {$false}
    }
    Write-Host
    if($verbose){$VerbosePreference = "Continue"}
}
Process{
    if($_)
    {
        Write-Host "Getting HBA Info from $_"
        if($FullDetail)
        {
            $MyObject = CreateHBAListObj $_
            $HBADetail = $MyObject | %{CreateHBAInfoObj $_.Host $_.PortWWN}
            $MyObject | add-Member -Name HBADetail -type NoteProperty -Value $HBADetail -force
            $MyObject
        }
        else
        {
            $MyObject = CreateHBAListObj $_
            $MyObject
        }
    }
}
End{
    if($list)
    {
        $servers = Get-Content $list
        Write-Host "Running HBA Check against Servers in $list"
        foreach($server in $servers)
        {
            if($server -ne "")
            {
                if(ping-server $server)
                {
                    Write-Host "Getting HBA Info from $server"
                    if($FullDetail)
                    {
                        $MyObject = CreateHBAListObj $server
                        $HBADetail = $MyObject | %{CreateHBAInfoObj $_.Host $_.PortWWN}
                        $MyObject | add-Member -Name HBADetail -type NoteProperty -Value $HBADetail -force
                        $MyObject
                    }
                    else
                    {
                        $MyObject = CreateHBAListObj $server
                        $MyObject
                    }
                }
                else
                {
                    Write-Host "$Server not Pingable `n" -foregroundcolor RED
                }
            }
        }
    }
    if($HostName)
    {
        Write-Host "Running HBA Check against Servers in $HostName"
        if(ping-server $HostName)
        {
            Write-Host "Getting HBA Info from $HostName"
            if($FullDetail)
            {
                $MyObject = CreateHBAListObj $HostName
                $HBADetail = $MyObject | %{CreateHBAInfoObj $_.Host $_.PortWWN}
                $MyObject | add-Member -Name HBADetail -type NoteProperty -Value $HBADetail -force
                $MyObject
            }
            else
            {
                $MyObject = CreateHBAListObj $HostName
                $MyObject
            }
        }
        else
        {
            Write-Host "$HostName not Pingable `n" -foregroundcolor RED
        }
    }
    Write-Host
}

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}

Citrix functions Updated!

I didn’t have time last post to additional “Farm” functionality to some the functions. The following are updated to work against multiple farms.

Updated Server functions

  • Publish-CitrixApplication
  • UnPublish-CitrixServer
  • Remove-CitrixApplication
  • Updated Application functions

  • Get-CitrixApp
  • Get-CitrixAppUsers
  • Get-CitrixAppServers
  • Find-CitrixUser
  • Citrix Server Functions

    ##########################################
    ####     Citrix Server Functions      ####
    ##########################################

    ## Publish Application to Server(s)
    ## -app: Name of Application to remove. This is required
    ## -Server: Name of Server
    ## PIPED: It will take Servers via Pipe. It expects a list or Citrix Server Object
    ## NOTE: App Name must include subfolders of the app.
    ##       If the app in in Applications\Test then app would be Test\MyApp
    ##       Example: Publish-CitrixApplication -server myserver -app Test\MyApp
    function Publish-CitrixApplication{
        Param([string]$server,[string]$app)
        Begin{
            Write-Host
            function gcs{
                Param($srv)
                $type = [System.Type]::GetTypeFromProgID("MetaframeCOM.MetaframeServer",$Server)
                $mfServer = [system.Activator]::CreateInstance($type)
                $mfServer.Initialize(6,$Server)
                $mfServer
            }
            function gca{
                Param($srv,$ma)
                $type = [System.Type]::GetTypeFromProgID("MetaFrameCOM.MetaFrameApplication",$Server)
                $mfApp = [system.Activator]::CreateInstance($type)
                $mfApp.Initialize(3,"Applications\$ma")
                $mfApp.LoadData($true)
                $mfApp
            }
            function cPublish {
                Param([string]$Srv,[string]$myapp)
                $Srv = $Srv.toUpper()
                $mfSrv = gcs $srv
                $mfApp = gca $srv $myapp
                $mfAppBinding = New-Object -ComObject MetaFrameCOM.MetaFrameAppSrvBinding
                $mfAppBinding.Initialize(6,$Srv,"Applications\$app")
                if($mfAppBinding)
                {
                    Write-Host "Publishing App[$myapp] on Server [$Srv]" -ForegroundColor Green
                    $mfApp.AddServer($mfAppBinding)
                    $mfApp.SaveData()
                }
                else
                {
                    Write-Host "Unable To Create App Binding" -ForegroundColor Red
                }
            }
            $process = @()
        }
        Process{
            if($_){
                if($_.ServerName){
                    $process += $_.ServerName
                }
                else{
                    $process += $_
                }
            }
        }
        End{
            if($Server){$Process += $Server}
            foreach($s in $process){
                cPublish -srv $s -myapp $app
                Write-Host
            }
        }
    }

    ## UnPublishes ALL Application from Server(s)
    ## -Server: Name of Server
    ## PIPED: It will take Servers via Pipe. It expects a list or Citrix Server Object
    function UnPublish-CitrixServer{
        Param([string]$server)
        Begin{
            function gcs{
                Param($srv)
                $type = [System.Type]::GetTypeFromProgID("MetaframeCOM.MetaframeServer",$Server)
                $mfServer = [system.Activator]::CreateInstance($type)
                $mfServer.Initialize(6,$Server)
                $mfServer
            }
            function cUnPublish {
                Param([string]$Srv)
                $Srv = $Srv.toUpper()
                $mfSrv = gcs $srv
                If($mfSrv.Applications.Count -gt 0)
                {
                    Write-Host "Removing All Published Applications from $Srv" -ForegroundColor Red
                    Write-Host "===================================================" -ForegroundColor Green
                    ForEach($a in $mfSrv.Applications)
                    {
                        $myApp = $a.AppName
                        Write-Host "Removing App [$myApp] from Server [$Srv]" -ForegroundColor White
                        $a.RemoveServer($Srv)
                        $a.SaveData()
                    }
                }
                else
                {
                    Write-Host "No Published Applications for $Srv" -ForegroundColor Red
                }
            }
            Write-Host
            $process = @()
        }
        Process{
            if($_){
                if($_.ServerName)
                {
                    $process += $_.ServerName
                }
                else
                {
                    $process += $_
                }
            }
        }
        End{
            if($Server){$Process += $Server}
            foreach($s in $process){
                cUnPublish $s
                Write-Host
            }
        }
    }

    ## Remove Specific Application from Server(s)
    ## -app: Name of Application to remove. This is required
    ## -Server: Name Server.
    ## PIPED: It will take Servers via Pipe. It expects a list or Citrix Server Object
    function Remove-CitrixApplication {
        Param([string]$server,[string]$app)
        Begin{
            function gcs{
                Param($srv)
                $type = [System.Type]::GetTypeFromProgID("MetaframeCOM.MetaframeServer",$Server)
                $mfServer = [system.Activator]::CreateInstance($type)
                $mfServer.Initialize(6,$Server)
                $mfServer
            }
            function RemoveApp {
                Param([string]$Srv,[string]$myapp)
                $AppRemoved = $false
                $Srv = $Srv.toUpper()
                $mfSrv = gcs $srv
                If($mfSrv.Applications.Count -gt 0)
                {
                    ForEach($a in $mfSrv.Applications)
                    {
                        If(($a.AppName -eq "$myapp") -or ($a.BrowserName -eq "$myapp"))
                        {
                            Write-Host "Removing App [$myApp] from Server [$Srv]" -ForegroundColor Green
                            $a.RemoveServer($Srv)
                            $a.SaveData()
                            $AppRemoved = $true
                        }
                    }
                }
                else
                {
                    Write-Host "No Applications Published for $Srv" -ForegroundColor Red
                    $AppRemoved = $true
                }
                If($AppRemoved -eq $false)
                {
                    Write-Host "This Application not Published for $Srv" -ForegroundColor Red
                }
            }
            Write-Host
            $process = @()
        }
        Process{
            if($_)
            {
                if($_.ServerName){

                    $process += $_.ServerName
                }
                else
                {
                    $process += $_
                }
            }
        }
        End{
            if($Server){$Process += $Server}
            if($process.Length -eq 0){$Process += get-content env:COMPUTERNAME}
            foreach($s in $process)
            {
                RemoveApp -Srv $s -myapp $app
                Write-Host
            }
        }
    }

    Citrix App Functions

    #########################################
    ####     Citrix App Functions        ####
    #########################################
    ## Returns Citrix Application for Farm
    ## -Server: Name of Farm Server. This is required
    ## -App: Name of Application to remove. This is required
    function Get-CitrixApp{
        Param($Server =$(throw ‘$Server is Required’),$App= $(throw ‘$FarmServer is Required’))
        $type = [System.Type]::GetTypeFromProgID("MetaFrameCOM.MetaFrameApplication",$Server)
        $mfApp = [system.Activator]::CreateInstance($type)
        $mfApp.Initialize(3,"Applications\$myapp")
        $mfApp.LoadData($true)
        $mfApp
    }

    ## Returns Users currently using APP
    ## -app: Name of Application. This is required
    ## -Server: Name of Farm Server. Defaults to local if not passed
    ## -count: Switch… if set just returns count of servers
    function Get-CitrixAppUsers {
        Param($app = $(throw ‘$app is required’),$server,[switch]$count)
        function gcf{
            param($srv)
            $type = [System.Type]::GetTypeFromProgID("MetaframeCOM.MetaframeFarm",$Server)
            $mfarm = [system.Activator]::CreateInstance($type)
            $mfarm.Initialize(1)
            return $mFarm
        }
        $ErrorActionPreference = "SilentlyContinue"
        Write-host
        if($server){$mfm = gcf $server}
        else{$mfm = New-Object -com MetaFrameCOM.MetaFrameFarm;$mfm.Initialize(1)}
        $users = $mfm.Applications | ?{($_.AppName -eq $app) -or ($_.BrowserName -eq $app)}
        $Users = $users.Sessions | sort -Property UserName
        if($count){
            Write-Host "Found [$($Users.Count)] Users for Application [$app]" -ForegroundColor White
            Write-Host
        }
        else{
            Write-Host ""
            Write-Host "Found [$($Users.Count)] Users for Application [$app]" -ForegroundColor White
            Write-Host "—————————————————–" -ForegroundColor gray
            foreach($user in $Users){
                If($User.SessionState -eq 1){
                    Write-Host ($User.UserName).PadRight(10) -ForegroundColor Green -NoNewline
                }
                else{
                    Write-Host ($User.UserName).PadRight(10) -ForegroundColor yellow -NoNewline
                }
            }
            Write-Host
            Write-Host "—————————————————–" -ForegroundColor gray
            Write-Host "Found [$($Users.Count)] Users for Application [$app]" -ForegroundColor White
            Write-Host
        }
    }

    ## Returns Servers currently published APP
    ## -app: Name of Application. This is required
    ## -Server: Name of Farm Server. Defaults to local if not passed
    ## -count: Switch… if set just returns count of servers
    function Get-CitrixAppServers {
        Param($app = $(throw ‘$app is required’),$Server,[switch]$count)
        function gcf{
            param($srv)
            $type = [System.Type]::GetTypeFromProgID("MetaframeCOM.MetaframeFarm",$Server)
            $mfarm = [system.Activator]::CreateInstance($type)
            $mfarm.Initialize(1)
            return $mFarm
        }
        if($server){$mfm = gcf $server}
        else{$mfm = New-Object -com MetaFrameCOM.MetaFrameFarm;$mfm.Initialize(1)}
        $Apps = $mfm.Applications | ?{($_.AppName -eq $app) -or ($_.BrowserName -eq $app)}
        # for XP farms
        $servers = $apps | %{$_.Servers} | sort -Property ServerName | Select-Object ServerName
        if(!$servers){
            # for 40/45 farms
            $servers = $Apps.Sessions | Select-Object ServerName | Sort-Object -unique ServerName
        }
        if($count)
        {
            Write-Host
            Write-Host "Found [$($Servers.Count)] Servers for Application [$app]" -ForegroundColor White
            Write-Host
        }
        else
        {
            Write-Host ""
            Write-Host "Found [$($Servers.Count)] Servers for Application [$app]" -ForegroundColor White
            Write-Host "———————————————–" -ForegroundColor gray
            foreach($server in $servers){Write-Host "$($server.ServerName)" -ForegroundColor Green}
            Write-Host "———————————————–" -ForegroundColor gray
            Write-Host "Found [$($Servers.Count)] Servers for Application [$app]" -ForegroundColor White
            Write-Host ""
        }
    }

    ## Returns Server(s) user is logged into via Citrix
    ## -LoginName: Login Name of user (Domain\User). This is Required
    ## -Server: Name of Farm Server. Defaults to local if not passed
    ## -Verbose: Details about User
    function Find-CitrixUser {
        Param([string]$LoginName,$Server,[switch]$verbose)
        $user = $LoginName.Split("\")[1]
        $Domain = $LoginName.Split("\")[0]
        if($server)
        {
            $type = [System.Type]::GetTypeFromProgID("MetaframeCOM.MetaframeUser",$Server)
            $mfuser = [system.Activator]::CreateInstance($type)
            $mfuser.Initialize(1,$Domain,1,$user)
        }
        else
        {
            $mfuser = New-Object -ComObject MetaframeCOM.MetaframeUser
            $mfuser.Initialize(1,$Domain,1,$user)
        }
        Write-Host
        Write-Host "User: $($mfuser.UserName) found on the Following:"
        foreach ($s in $mfuser.Sessions)
        {
            if($verbose)
            {
                Write-Host
                Write-Host "$($s.ServerName)"
                Write-Host "-=-=-=-=-=-"
                Write-Host "AppName          : $($s.AppName)" -foregroundcolor yellow
                Write-Host "SessionName      : $($s.SessionName)" -foregroundcolor yellow
                Write-Host "SessionID        : $($s.SessionID)" -foregroundcolor yellow
                Write-Host "ClientAddress    : $($s.ClientAddress)" -foregroundcolor yellow
                Write-Host "ClientEncryption : $($s.ClientEncryption)" -foregroundcolor yellow