Showing posts with label powershell. Show all posts
Showing posts with label powershell. Show all posts

Thursday, November 19, 2015

Monitoring your CDN's Webservers

(This post considers options for writing a monitor in powershell that can handle round-robin DNS load-distributed web servers, especially those that you do not control)

Premise

After observing some issues with a vendor's CDN having occasional server/service outages, I decided to write some powershell to dig through and give me a quick analysis so I could determine where the problem was and what effect it should have on the users. Some of you may read this and think "If the site is down, it's down, right?"

Well many websites now spread their resources across CDNs (Content Delivery Networks). This means that while the html page may come from one webserver (or even one CDN), the javascript and CSS file may come from another, and the images and videos from yet another. You can use the Developer Tools in your web browser to dig up out the sources for (at least THIS load) of the website.

On top of this, there are a few approaches to load balancing, where the exact same resources are duplicated across many servers, with the intent of spreading traffic around and reducing the chances of downtime. Citrix's Netscaler is a popular (and VERY capable) option, but sometimes you'll see the vendor go down the cheaper and easier path: Round-Robin DNS.

Approach

In Powershell, you can invoke the GetHostAddresses method to see how many IP addresses are linked to a DNS entry.

[System.Net.DNS]::GetHostAddresses("google.com").ipAddressToString

At the time of this writing, that returns 11 IP addresses. If you want to dig into nslookup, you can watch it cycle through the IPs in that list as you repeatedly query for google.com.

So what if your CDN is using Round Robin, and your customers are complaining of an outage, and the site appears up?


function test-CDNPage{
    Param
        ([string]$url) #Properly Formatted with http:// or https://
    try{
        write-host -ForegroundColor Cyan "Trying $url"
        Invoke-WebRequest -Uri $url
    }
    Catch [System.Net.WebException]
    {
    if ($_.exception -match "403")
        {
            # 403 is expected because CDNs typically dont allow IP access
            Write-host "403" -ForegroundColor Green
            $value = $true
        }
        ELSE
        {
            # A non-403 response is treated as 
            write-host "Non-403 Response" -ForegroundColor Red
            $_
            $value = $false
        }   
    }
    #Add Results to Object
    $thisResult = New-Object psobject -property @{
        url        = $url
        Responding = $value
        }
    return $thisResult
}

$data = @()
$site = "google.com"
$ipaddys = [system.net.dns]::GetHostAddresses($site).IPAddressToString
foreach ($ip in $ipaddys){
    $data += testpage "http://$ip"
}


Now this code probably won't return anything because Google isn't a CDN. They see you trying to access the IP directly and re-direct your web browser to https:\\google.com. They likely also have some smarter handling behind their Round-Robin DNS solution, as they are...Google.

But consider a company like CloudFlare. They may be hosting multiple different websites from multiple different companies on their servers.

When you try to access a site like that, you load a page telling you that "direct IP access is not allowed" in your browser, and powershell's Invoke-WebRequest throws a 403 Terminating error. This is where Powershell can do things that some other monitoring tools can not. The powershell snippet above uses a try/catch statement to look at the error. If the error is a 403, it sets the $value to true. If the error is NOT a 403, it sets the $value to false. I've set this up to output an XML file every minute that is consumable by my client's monitoring system, and now they can see when individual pieces of their vendor's web services go down.

Having solid data like this can frequently get you past the first tier support layer, that at many companies is incapable of solving network issues on their own end, and is typically only a fence I must cross to reach an actual solution.

Monday, May 18, 2015

Custom Event Logs

Creating a new EventLog in powershell

Create the new log:
New-EventLog -LogName MyEventLogName -Source scripts
See that it exists:
Get-EventLog -List
Write to it:
Write-EventLog -LogName MyEventLogName -Source scripts -Message "Dude, it works ... COOL!" -EventId 0 -EntryType information
And then pull the events from the log:
Get-EventLog -LogName MyEventLogName
Eventlogs created in this manner will show under the "Applications and Services Logs" branch in the Event Viewer (which can be launched with "show-eventlog"). You can use either the GUI, or the "Limit-EventLog" cmdlet to make changes to the size and retention options for the log (shown with get-eventlog -list)

Custom Permissions on Eventlogs

In Windows 2008 the SDDL is set with the "wevtutil" app.
To begin, we need to get the current SDDL string. It's the "ChannelAccess" line in the output of the following command:
wevtutil gl MyEventLogName
Copy the channelAccess: line into the text editor of your choice:

channelAccess: O:BAG:SYD:(A;;0xf0007;;;SY)(A;;0x7;;;BA)(A;;0x5;;;SO)(A;;0x1;;;IU)(A;;0x1;;;AU)(A;;0x1;;;SU)(A;;0x1;;;S-1-5-3)(A;;0x2;;;LS)(A;;0x2;;;NS)(A;;0x2;;;S-1-5-33)(A;;0x1;;;S-1-5-32-573) )

Each of the (A;;xxx;;;x-x-x-xx) strings is a SDDL permission, the first "xxx" sets the permission, according to this:
Clear 0x4
Read 0x1
Write 0x2

The second "x-x-x-xx" is a user or group SID. Depending on the SID's source, it may be longer (for instance if it's coming from active directory). There is a list of "well-known" SIDs here. Build a new SDDL permission, and tag it onto the end of the existing string.

O:BAG:SYD:(A;;0xf0007;;;SY)(A;;0x7;;;BA)(A;;0x5;;;SO)(A;;0x1;;;IU)(A;;0x1;;;AU)(A;;0x1;;;SU)(A;;0x1;;;S-1-5-3)(A;;0x2;;;LS)(A;;0x2;;;NS)(A;;0x2;;;S-1-5-33)(A;;0x1;;;S-1-5-32-573) (A;;0x1;;; S-1-5-3-3127463467463))

Finally, we need to apply the new SDDL. Paste your modified SDDL String (complete with the O:BAG:SYD on the front) after the /ca:
wevtutil sl MyEventLogName /ca:
Attempting to set the SDDL from powershell will cause a mess of syntax errors, due to the powershell interpreter trying to parse the SDDL string. This may be avoidable with single-quotes, but I just ran "cmd", re-ran the command, and then ran "exit" to return to powershell.
Now with a new eventlog with custom permissions, you can use write-eventlog to write output from your powershell scripts.

Sources:
Jane Lewis Blog
Hey Scripting Guy Blog

Thursday, October 9, 2014

Update Existing PST file path for Users

Replaced an aging file server, and ran into the issue of Terminal Services users not being able to load their Outlook pst data files. After much surfing, searching, and a call to Microsoft, I still had nothing. Thanks to a co-worker and mucho caffeine, we finally located the reg keys (in binary) that stored the pst location.

The script below is tested against Outlook 2010, but has been successful in my testing. Set the $oldpath and $newpath to fit your environment. Test it against a few accounts before deploying. I take no responsibility for the use of this in any org. :D

$encodingType = [Text.Encoding]::Default.HeaderName
$oldPath = "" #Old fileserver/path name here
$newPath = "" #New fileserver/path name here


function bin2string{
    Param(
        $thispath, #Path to the Key
        [string]$thisProperty #Name of the property (typically in HEX)
    )
    $binary = Get-ItemProperty -path $thisPath.pspath | select -expand $thisProperty
    $string = [Text.Encoding]::GetEncoding($encodingType).GetString( $binary )
    return $string
}

function string2bin{
    param(
        [string]$thisString
    )
    $newBinary = [Text.Encoding]::GetEncoding($encodingType).GetBytes( $thisString )
    return $newBinary
}


set-location "HKCU:\Software\Microsoft\Windows NT\CurrentVersion\Windows Messaging Subsystem\Profiles\Default Outlook Profile"
$path = get-location


$keys = get-childitem $path

foreach($key in $keys){
    #write-host $key -ForegroundColor DarkCyan
    $properties = get-itemproperty -path $key.pspath
    #$properties | where {$_ -match "01020fff"}
    if (($properties | where {$_ -match "01020fff"}).count -ne 0 -AND ($properties | where {$_ -match "001f3006"}).count -ne 0){
        #Make sure this is an Outlook Data File Key
        if ((bin2string $key "001f3006") -match "O\WU\WT\WL\WO\WO\WK\W+D\WA\WT\WA\W+F\WI\WL\WE"){
            Write-Host "Found $(bin2string $key "001f3006") in $Key" -ForegroundColor Yellow

            #Get string from Property 01020fff, the functional Path property
            $nowString = bin2string $key "01020fff"
            #Fix string in Property 01020fff, the functional Path property
            $nowString = $nowString -replace $oldPath,$newPath
            #Convert Fixed String Back into Binary
            $newBinary = string2bin $nowString
            #Set the Bin as the Value of Property 01020fff, the functional Path property
            Set-ItemProperty -path $key.pspath  -name "01020fff" -value $newBinary

            #Get string from Property 001f6700
            $nowString2 = bin2string $key "001f6700"
            $nowString2
            #Fix string in Property 001f6700
            $nowString2 = $nowString2 -replace ($oldPath.ToCharArray() -join "\W"),($newPath.ToCharArray() -join " ")
            $nowString2
            #Convert Fixed String Back into Binary
            $newBinary2 = string2bin $nowString2
            #Set the Bin as the Value of Property 001f6700
            Set-ItemProperty -path $key.pspath  -name "001f6700" -value $newBinary2

            }
        }
    }

Wednesday, October 1, 2014

Updating Shortcuts for a File Server replacement

When retiring an older File Server, one has to account for the many possible uses and references to the old file server name. Shortcuts is one that can be addressed via Powershell. The following script finds all shortcuts that have the $from server, and update the references to point to the $to server (these are set in the script beforehand). The only catch is that I didn't build in long file name support, so if you have paths over 255 characters, some additions will be required.
<# 
 .NOTES
 ===========================================================================
  Author: Jared Shippy
  Created: 09/19/2014
 ===========================================================================
 .DESCRIPTION
  Script for updating shortcuts for new file server location
 .PARAMETER Path
  Path to target folder.
 .PARAMETER Recurse
  Updates shortcuts through subfolders if used.
 .PARAMETER Whatif
  Writes no changes, displays files that would be affected.
 .EXAMPLE
  # See which shortcuts would be affected in c:\testFolder (subfolders included)
  lnkUpdate -path c:\testFolder -recurse -whatif
 .EXAMPLE
  # Make the perform the change in c:\testfolder ONLY (no subfolders)
  lnkUpdate -path c:\testfolder
#>

param (
 # Path to root of Targeted Folder
 [Parameter(Mandatory=$true)]
 [string]$path,
 # Perform Recursive?
 [switch]$recurse,
 # Does nothing, shows what it *would* do
 [switch]$whatif
)

# Initialization Variables
$shell = New-Object -ComObject wscript.shell
$from = "oldServer" #The name of the old file server
$to = "newServer" #The name of the new file server

# Targeting Logic - To recurse or not recurse
$lnkFiles = $null
if ($recurse -eq $true)
 {
  $lnkFiles = Get-ChildItem -Recurse (Join-Path $path \*.lnk)
 }
 else
 {
  $lnkFiles = Get-ChildItem (Join-Path $path \*.lnk)
}
foreach ($file in $lnkFiles)
{
 
 if (($shell.createShortcut($file.fullname)).targetPath -match "$from" -or ($shell.createShortcut($file.fullname)).workingdirectory -match "$from" -or ($shell.createShortcut($file.fullname)).arguements -match "$from")
 
 {
  Write-Host "Match on $($file.fullname)" -ForegroundColor 'DarkCyan'
  if ($whatif -eq $true)
  {
   Write-Host "WHATIF: WOULD replace path here"
  }
  else
  {
   $fixThis = $shell.createShortcut($file.fullname)
   $fixThis.targetPath = $fixThis.targetPath -replace "$from", "$to"
   $fixThis.workingDirectory = $fixThis.workingDirectory -replace "$from", "$to"
   $fixThis.arguments = $fixThis.arguments -replace "$from", "$to"
   $fixThis.Save()
  }
  
 }
}



# check and update links
foreach ($shortcut in $shortcuts)
{
 # Find the correct property for the next line:
 If ($shortcut.property -match "$from")
 {
  $shortcut.property -replace "$from", "$to"
 }
 else
 {
  Write-Host "No $from in this Shortcut" -ForegroundColor Green
  
 }
}
 


Friday, July 18, 2014

Detecting Cryptolocker/Cryptowall by encrypted files

Have you encountered the Crypto line of ransomware? Having encountered a couple variants of this with a few of my customers, I began wondering how one would detect an infection in an enterprise environment, where recent backups are available, but users *may* not advertise they caught a virus. Detecting the encrypted files before all of your backups are overwritten would be important. How would we do this?

File headers combined with write times appears to be an effective means detection, as Encrypted file headers do not match even each-other, and write-times will be VERY close to eachother when a crypto-ransomware hits, so write-times helps weed out false positives.

Turning to powershell, I borrowed from Will at http://learningpcs.blogspot.com/2012/07/powershell-v3-check-file-headers.html. I made some minor modifications to his script, limiting the file types it targets, and causing it to search recursively through the directory tree of it's target. Enable verbose messaging and you will see the headers for any mismatches, so you can add your own if needed. This script will ONLY tell you about mismatches.


<
# http://learningpcs.blogspot.com/2012/07/powershell-v3-check-file-headers.html
# http://garcol.blogspot.com/2014/07/detecting-cryptolockercryptowall-by.html
function Check-Header
{
       param(
             $path
       )
      
       # Hexidecimal signatures for expected files
       $pdf = '25504446';
       $doc = 'D0CF11E0';
       $docx = '504B0304';
       $xls = 'D0CF11E0';
       $xls2 = '093C7461';
       $xlsx = '504B0304';

       
       $targets = Get-ChildItem -file -Path $path -recurse -include ('*.pdf','*.doc','*.docx','*.xls','*.xlsx') | select fullname, lastwritetime
       foreach ($file in $targets){     
            # Get content of each file (up to 4 bytes) for analysis
            $HeaderAsHexString = $null
            [Byte[]]$fileheader = Get-Content -Path $file.fullname -TotalCount 4 -Encoding Byte
            ForEach($_ in $fileheader) {
                if(("{0:X}" -f $_).length -eq 1)
                    {
                     $HeaderAsHexString += "0{0:X}" -f $_
                    }else{
                     $HeaderAsHexString += "{0:X}" -f $_
                    }
            }
       # Validate file header
       if (@($pdf, $doc, $docx, $xls, $xls2, $xlsx) -contains $HeaderAsHexString){
            #do nothing
            }else{
            $file
            Write-Verbose -message $HeaderAsHexString
            } 
       
    }
}


Thursday, July 3, 2014

Powershell Scripting Telnet

There are some of us out there that use appliances, equipment, or software that is still stuck with the telnet protocol. Where there are options for scripting interactions with webpages, SSH, and many other resources, I did not find many options for telnet scripting, and I'm not at the point in powershell where I could whip up a telnet client in powershell. Fortunately, Grant Carthew has already done the heavy lifting here. Below you can find a modified version of his script that allows scripting interactions through the telnet protocol, retrieving the responses so that logic can be performed on them.

Credits to the Ugly Gizmo blog, this script would not be without Grant's work.
<#
.SYNOPSIS
A modified graceful telnet scripting client using PowerShell and the .NET Framework.
 
.DESCRIPTION
This script was originally made with a view of using it to have full control over the text
stream for automating Cisco router and switch configurations. Modifications have been
added for scripting automation and logic
 
.PARAMETER TelnetHost
The address of the server or router hosting the telnet service.
 
.PARAMETER Port
The TCP port number of the Telnet service running on the Telnet host.

.NOTES
Modified: http://garcol.blogspot.com/2014/07/powershell-scripting-telnet.html
Original: http://uglygizmo.blogspot.com/2013/10/a-telnet-client-written-in-powershell.html
#>


 
param
(
    [parameter(Mandatory)]
    [String]
    $TelnetHost,
    [Int]
    $TelnetPort = 23
)
 
# Initialize variables
[System.Text.ASCIIEncoding]$ASCIIEncoding = [System.Text.Encoding]::ASCII
[System.Net.Sockets.Socket]$Socket = $null
 
# Checking host address and port.
if ($TelnetHost -match ":")
{
    $hostBytes = $TelnetHost.Split(':')
    $TelnetHost = $hostBytes[0]
    $TelnetPort = $hostBytes[1]
}
if ([String]::IsNullOrEmpty($TelnetHost))
{
    Write-Host -Object "Error: Invalid host address (null or empty)." -ForegroundColor Red
    return
}
 
# Setup and connect the TCP Socket.
$Socket = New-Object -TypeName System.Net.Sockets.Socket(
    [System.Net.Sockets.AddressFamily]::InterNetwork,
    [System.Net.Sockets.SocketType]::Stream,
    [System.Net.Sockets.ProtocolType]::Tcp)
$Socket.NoDelay = $true
try
{
    $Socket.Connect($TelnetHost, $TelnetPort)
}
catch
    {
    "Unable to connect to host {0}:{1}" -f $TelnetHost,$TelnetPort
    return
}
 
# This state object is used to pass the connected Socket and the
# PowerShell parent Host reference to the child PowerShell object.
$State = [PSCustomObject]@{"Socket"=$Socket;"Host"=$Host;"Exception"=$null;"Response"=$null}
 
# This script block is used as the receive code for the Socket
# from within the child PowerShell object.
$Script = {
    param($state)
    [string[]]$returnLog = "Started"
    # This encoding object is used to decode the return string.
    [System.Text.ASCIIEncoding]$ASCIIEncoding = [System.Text.Encoding]::ASCII
 
    # TELNET commands
    [Byte]$GA = 249 # Go Ahead
    [Byte]$WILL = 251 # Desire to begin
    [Byte]$WONT = 252 # Refusal to perform
    [Byte]$DO = 253 # Request that the other party perform
    [Byte]$DONT = 254 # Demand that the other party stop performing
    [Byte]$IAC = 255 # Interpret as Command
 
    # TELNET options
    [Byte]$ECHO = 1 # Used to check the echo mode
    [Byte]$SUPP = 3 # Suppress go ahead
    # Used to hold the number of bytes returned from the network stream.
    [Int]$bytes = 0
    # Buffer to hold the returned Bytes.
    [Byte[]]$buffer = New-Object -TypeName Byte[]($state.Socket.ReceiveBufferSize)
    # This is the main receive loop.
    while ($state.Socket.Connected)
    {
        try
        {
        # The following statement will block the thread until data is received.
        $bytes = $state.Socket.Receive($buffer)
        }
        catch
        {
        # This exception reference is used to pass the error back to the
        # parent PowerShell process.
        $state.Exception = $Error[0]
        break
        }
 
        if ($bytes -gt 0)
        {
            $index = 0
            $responseLen = 0
            # The index is used to move through the buffer to analyze the received data
            # looking for Telnet commands and options.
            while ($index -lt $bytes)
            {
                if ($buffer[$index] -eq $IAC)
                {
                    try
                    {
                        switch ($buffer[$index + 1])
                        {
                            # If two IACs are together they represent one data byte 255 
                            $IAC
                            {
                                $buffer[$responseLen++] = $buffer[$index]
                                $index += 2
                                break
                            }
                            # Ignore the Go-Ahead command
                            $GA
                            {
                                $index += 2
                                break
                            }
                            # Respond WONT to all DOs and DONTs
                            {($_ -eq $DO) -or ($_ -eq $DONT)}
                            {
                                $buffer[$index + 1] = $WONT
                                $state.Socket.Send($buffer, $index, 3,
                                [System.Net.Sockets.SocketFlags]::None) | Out-Null
                                $index += 3
                                break
                            }
                            # Respond DONT to all WONTs
                            $WONT
                            {
                                $buffer[$index + 1] = $DONT
                                $state.Socket.Send($buffer, $index, 3,
                                [System.Net.Sockets.SocketFlags]::None) | Out-Null
                                $index += 3
                                break
                            }
                            # Respond DO to WILL ECHO and WILL SUPPRESS GO-AHEAD
                            # Respond DONT to all other WILLs
                            $WILL
                            {
                                [Byte]$action = $DONT
 
                                if ($buffer[$index + 2] -eq $ECHO)
                                {
                                    $action = $DO
                                }
                                elseif ($buffer[$index + 2] -eq $SUPP)
                                {
                                    $action = $DO
                                }
 
                                $buffer[$index + 1] = $action
                                $state.Socket.Send($buffer, $index, 3,
                                    [System.Net.Sockets.SocketFlags]::None) | Out-Null
                                $index += 3;
                                break;
                            }
                        }
                    }
                    catch
                    {
                        # If there aren't enough bytes to form a command, terminate the loop.
                        $index = $bytes
                    }
                }
                else
                {
                    if ($buffer[$index] -ne 0)
                    {
                        $buffer[$responseLen++] = $buffer[$index]
                    }
                    $index++
                }
            }
            # Displays the response with no command codes on the parent PowerShell object.
            $returnString = $ASCIIEncoding.GetString($buffer, 0, $responseLen)
            $returnLog += $returnString
            #$state.Host.UI.Write($returnString)
            $state.response += $returnString
        }
    }
} # End of the child PowerShell script definition.
 
# Create a child PowerShell object to run the background Socket receive method.
$PS = [PowerShell]::Create()
$PS.AddScript($Script).AddArgument($State) | Out-Null
[System.IAsyncResult]$AsyncJobResult = $null
try
{
    # The receive job is started asynchronously.
    $AsyncJobResult = $PS.BeginInvoke()



    #YOUR TELNET COMMANDS IN HERE

    # Sends Commands
    function sendCmd($command)
    {
        # Wait, then send data.
        start-sleep -m 500
        $Data = $ASCIIEncoding.GetBytes($command)
        $Socket.Send($Data) | Out-Null
        $Socket.Send(13) | Out-Null #sends the return keystroke after each cmd
        start-sleep -m 500
    }

    ######################################################################################
    #TELNET COMMANDS IN HERE
    # Use sendCmd("") to send text commands, use $Socket.Send() to send ascii encoded keystrokes
    #

    # List of Commands to be Sent
    [string[]]$cmdList = "username"
    $cmdlist += "password"
 
    # Login
    foreach ($cmd in $cmdlist){ sendCmd($cmd) | out-null}
    

    sendCmd("show something") | out-null
    $thisData = $state.response #Can perform logic on the response here
    $state.response = $null
    
    # Issue the Exit command, closing the session gracefully
    sendCmd("exit") | out-null


    #
    #TELNET COMMANDS IN HERE
    ######################################################################################





}
finally
{   
    # Cleanup the socket and child PowerShell process.
    if ($Socket -ne $null)
    {
        $Socket.Close()
        $Socket.Dispose()
        $Socket = $null
    }
 
    if ($PS -ne $null -and $AsyncJobResult -ne $null)
    {
        $PS.EndInvoke($AsyncJobResult)
        $PS.Dispose()
    }
    
}

Tuesday, April 8, 2014

get-XALoadDetails

I wanted a tool to be able to pull load information from the different cmdlets available in the XenApp powershell snapin, I also took the opportunity to learn how powershell modules work. Grabbing from get-xaserver, get-xaloadevaluator, and get-xaserver it produces the following output:

LoadEval LoginStatus   Load     Sessions Max ServerName
-------- -----------   ----     -------- --- ----------
Default  AllowLogOns   2100           21 100 XenAppSvr

It accepts pipeline input (from get-xaworkergroupserver for instance) and includes handling for the snap-in to avoid the getting the snap-in scope-locked.



xenModules.psm1
<?ps
#Requires -version 2.0
#Requires –PSSnapin Citrix.XenApp.Commands


function get-XALoadDetails{
    #.Synopsis
        # Gathers XenApp Load Evaluation Data into a single object
    #.Description
        # Gathers XenApp Load Evaluation Data into a single object, useful for determining load balancing behavior. Requires Citrix Xenapp Powershell snapin.
    #.Parameter Server
        # Server to pull load Evaluation data from, or enter * or all, to poll the entire farm.
    #.Parameter HandleSnapin
        # Loads and unloads the Citrix.XenApp.Commands snapin for the script. Default is false.
    #.Example
        # Show Load Balancing Details for server "2K8-Hamster"
        # Get-XALoadDetails -Server 2K8-Hamster
    #.Example
        # Show Load Balancing Details for all XenApp Servers
        # Get-XALoadDetails -Server All
 
  [cmdletbinding()]
    param(
            # Target Server, or * to pull all Xen Servers
            [Parameter(Position=0, Mandatory=$True, ValueFromPipeline=$True)]
            [string[]]$server,
            # Handle loading the snapin? Default is no.
            [Parameter(Position=1, Mandatory=$False)]
            [Alias("LS")]
            [switch]$handleSnapin
       )
    BEGIN{
        #Handling the Citrix Snapin. If the $handleSnapin is called, CHECK to see if the snapin is loaded before continuing
        #If the snapin was already loaded, set $handleSnapin to $false
            if($handleSnapin -eq $true){
                if ((Get-PSSnapin -name Citrix.XenApp.Commands -ErrorAction SilentlyContinue) -eq $null){
                    Add-PSSnapin Citrix.XenApp.Commands
                    }ELSE{
                    $handleSnapin = $false
                    }
            }
 
        #{Add-PSSnapin citrix*}
        $allLoads = Get-XAServerLoad #grab load info ONCE
        
         
         
                                                         
    }PROCESS{
        
        [string[]]$servers = " "
        if ($server -eq "*" -or $_ -eq "all")
            {$servers = Get-XAServer | sort ServerName | select -expand servername}
            ELSE
            {$servers = $server}
                
        
          #if (($server -eq "*") -or ($server -eq "all")){$server = Get-XAServer | sort ServerName | select -expand servername}
          $servers
        foreach ($server in $servers){
        if((Test-Connection -count 1 ($server) -quiet) -eq $true){
            "yup"
            $3in1 = get-xaserver ($server) -full | select ServerName, SessionCount, LogOnMode
            $allEvals = get-xaloadevaluator -server ($3in1.ServerName) | select ServerUserLoad, LoadEvaluatorName
            $obj = New-Object PSObject -Property @{
                MaxSessions = $allEvals.ServerUserLoad
                LoadEvaluator = $allEvals.LoadEvaluatorName
                LoginStatus = $3in1.LogOnMode
                Load = $allLoads | where{($3in1.ServerName) -eq $server.ServerName} | select -expand Load
                Sessions = $3in1.SessionCount
                ServerName = $3in1.Servername
                }
            $obj.pstypenames.Insert(0,'xenModule.LoadDetails')
            $obj
             
            }

        }
 
    }END{
    #Remove PSSnapin if it wasn't loaded before, to avoid scope locking
    if($handleSnapin -eq $true){Remove-PSSnapin Citrix.XenApp.Commands
        }
    }
 
}##End of get-XALoadDetails Function
export-modulemember -function get-XALoadDetails


xenModules.psd1
<?ps
#Requires –PSSnapin Citrix.XenApp.Commands
#
# Module manifest for module 'xenModules'
#
# Generated by: Jared Shippy
#
# Generated on: 04/03/2014
#

@{

# Script module or binary module file associated with this manifest
# RootModule = ''

ModuleToProcess = 'xenModules.psm1'

# Version number of this module.
ModuleVersion = '1.0'

# ID used to uniquely identify this module
#GUID = 'd0a9150d-b6a4-4b17-a325-e3a24fed0aa9'

# Author of this module
Author = 'Jared Shippy'

# Company or vendor of this module
#CompanyName = 'Unknown'

# Copyright statement for this module
#Copyright = '(c) 2012 User01. All rights reserved.'

# Description of the functionality provided by this module
# Description = ''

# Minimum version of the Windows PowerShell engine required by this module
#PowerShellVersion = '2'

# Name of the Windows PowerShell host required by this module
# PowerShellHostName = ''

# Minimum version of the Windows PowerShell host required by this module
# PowerShellHostVersion = ''

# Minimum version of the .NET Framework required by this module
# DotNetFrameworkVersion = ''

# Minimum version of the common language runtime (CLR) required by this module
# CLRVersion = ''

# Processor architecture (None, X86, Amd64) required by this module
# ProcessorArchitecture = ''

# Modules that must be imported into the global environment prior to importing this module
# RequiredModules = @()

# Assemblies that must be loaded prior to importing this module
# RequiredAssemblies = @()

# Script files (.ps1) that are run in the caller's environment prior to importing this module
# ScriptsToProcess = @()

# Type files (.ps1xml) to be loaded when importing this module
# TypesToProcess = @()

# Format files (.ps1xml) to be loaded when importing this module
FormatsToProcess = @('xenModules.Format.ps1xml')

# Modules to import as nested modules of the module specified in RootModule/ModuleToProcess
# NestedModules = @()

# Functions to export from this module
FunctionsToExport = '*'

# Cmdlets to export from this module
CmdletsToExport = '*'

# Variables to export from this module
VariablesToExport = '*'

# Aliases to export from this module
AliasesToExport = '*'

# List of all modules packaged with this module
# ModuleList = @()

# List of all files packaged with this module
# FileList = @()

# Private data to pass to the module specified in RootModule/ModuleToProcess
# PrivateData = ''

# HelpInfo URI of this module
# HelpInfoURI = ''

# Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix.
# DefaultCommandPrefix = ''

}


xenModules.Format.ps1xml
<?xml



  

  HashTable
  
    XenModule.LoadDetails
  
  
    
      
        
        8
      
      
        13
      
      
        8
      
      
        8
        Right
      
      
        
        3
      
      
        10
      
    
    
      
        
           
             LoadEvaluator
           
           
             LoginStatus
           
           
             Load
           
           
             Sessions
           
           
              MaxSessions
           
           
             ServerName