Posts RSS Comments RSS 130 Posts and 202 Comments till now

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
                Write-Host
                Write-Host "Processes"
                Write-Host "========="
                foreach ($proc in $s.Processes)
                {
                    Write-Host $proc.ProcessName -foregroundcolor Green
                }
                Write-host
            }
            else
            {
                write-Host "   -> $($s.ServerName)"
            }
        }
    }

    Test-Port (kinda like portqry without verbose output)

    We had a little dicussion on www.powershelllive.com forums about the most efficient way to Test a machine before trying a WMI query against it (as it has a log timeout.) My first suggestion was to use a ping (WMI style) but Jeff from http://blog.sapien.com brought up a valid point… what if ICMP is NOT Allowed…

    Enter Test-Port. This nifty little script uses the TCPClient Class to test connectivity. Stay tuned as I am planning some mods.

    Test-Port
    - Takes parameter $srv for Server Name
    - Takes Parameter for Port, Defaults to 135 for RPC mapper.
    - Takes Timeout.. defaults to 3000 (miliseconds)
    - If it cannot connect within timeout… Returns $false
    - If it gets exception connecting to port… Returns $false
    - If it connects… Returns $True

    function Test-Port{
        Param([string]$srv,$port=135,$timeout=3000,[switch]$verbose)
        $ErrorActionPreference = "SilentlyContinue"
        $tcpclient = new-Object system.Net.Sockets.TcpClient
        $iar = $tcpclient.BeginConnect($srv,$port,$null,$null)
        $wait = $iar.AsyncWaitHandle.WaitOne($timeout,$false)
        if(!$wait)
        {
            $tcpclient.Close()
            if($verbose){Write-Host "Connection Timeout"}
            Return $false
        }
        else
        {
            $error.Clear()
            $tcpclient.EndConnect($iar) | out-Null
            if($error[0]){if($verbose){write-host $error[0]};$failed = $true}
            $tcpclient.Close()
        }
        if($failed){return $false}else{return $true}
    }

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

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

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

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

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

    Run-Command.ps1

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

    Powershelling Citrix (The Good, Bad, and The Code)

    I would have to say that my first real experience with POSH was specifically with Citrix. As an Admin (one of many hats as a network engineer) of over 400 Citrix Servers and 3 Farms… I tend to script a alot. I am not a real fan of the CMC (Citrix Managment Console) and I much prefer anything that does not involve a GUI.

    One of my biggest problems is that the three farms are isolated from each other. That makes what ever I do 3x the work. In this aspect… POSH has been a savior. I can write the script once… and then cut/paste. It’s awesome.

    The Good:

    If there is one thing I like about Citrix (and thats about it) is the fact they wrote a series of COM interfaces for Metaframe. These are incredibly useful but until POSH you couldn’t access these from the Command Shell. POSH lets you create COM objects on the fly so I use the Metafram COM interfaces exclusively.

    The Bad:

    As in all things Citrix… The COM interface is quirky (do I sound bitter?) I have run into a couple of little snags that if avoided will make your life alot easier.

    1) Case Sensitive: I have found in my Citrix Use that for some reason. When you use MetaFrameCOM.MetaFrameServer and Initialize it… the Server Name must be in all CAPS. Not sure why (sorry) but I know if you don’t .ToUpper the Server… it failes.

    2) Initialize… Initialize… Initialize: Almost all of the MetaFrameCOM component’s require you to Initialize… this would normally wouldn’t be a problem, but the finding the Initialization codes was a bit a of a pain. (I posted them in the appendix.)

    3) For none developers passing an object as a parameter (who’d of thunk it?): So… for you C# people this is normal, but for the rest u sof scripters… not so much. You can see example of this in my Publish-CitrixApplication Function at the line $mfApp.AddServer($mfAppBinding). You have to create an App Binding Object to Add the server.

    The Code:

    Name: Get-CitrixOnline
    Purpose: Get All Citrix Servers Currently Online (not to be confused with published)

    function Get-CitrixOnline {
       Param([string]$zone)
       $mfzone = New-Object -ComObject MetaFrameCOM.MetaFrameZone
       $mfzone.Initialize("$zone")
       $servers = $mfzone.OnlineServers
       $servers | sort ServerName | Write-Output
    }

    Notes: Nothing special here, but make sure you know the zone name. You can get it from the CMC, but it should be the network of the first Citrix Server (i.e. 192.168.0.0)

    Name: Get-CitrixApplications
    Purpose: Get Citrix Apps published on Server

    function Get-CitrixApplications {
       Param([string]$server)
       $Server = $Server.ToUpper() # Citrix requires Server in CAPS (how odd)
       $mfsrv = New-Object -ComObject MetaFrameCOM.MetaFrameServer
       $mfsrv.Initialize(6,"$Server")
       Write-Host "SERVER $Server" -foregroundcolor Red
       Write-Host "==================" -ForegroundColor Green
       If($mfSrv.Applications.Count -gt 0) {
          $mfSrv.Applications %{Write-Host "Published: $($_.AppName.ToUpper())"}
       }
       else {
          Write-Host "No Applications Published for $Server" -foregroundcolor white
       }
    }

    Notes: Very useful for getting quick idea at what apps a server has published.

    Name: Publish-CitrixApplications
    Purpose: Publish Citrix App on Server

    function Publish-CitrixApplication {
       Param([string]$Srv,[string]$myapp)
       $Srv = $Srv.toUpper()
       $mfSrv = New-Object -ComObject MetaFrameCOM.MetaFrameServer
       $mfSrv.Initialize(6,"$Srv")
       $mfApp = New-Object -ComObject MetaFrameCOM.MetaFrameApplication
       $mfApp.Initialize(3,"Applications\$myapp")
       $mfApp.LoadData($true)
       $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
       }
    }

    Notes: Thing to be careful with this one is the $mfApp.Initialize(3,”Applications\$myapp”) It is important to know that “Application\$myApp” needs to be the path you see in the CMC. If you created subfolders then you need to include them like $mfApp.Initialize(3,”Applications\Lab\$myapp”).

    Name: UnPublish-CitrixServer
    Purpose: Remove All Citrix Apps from Server

    function UnPublish-CitrixServer {
       Param([string]$Server)
       Write-Host   $Server = $Server.toUpper()
       $mfSrv = New-Object -ComObject MetaFrameCOM.MetaFrameServer
       $mfSrv.Initialize(6,"$Server")
       If($mfSrv.Applications.Count -gt 0) {
          Write-Host "Removing All Published Applications from $Server"
          Write-Host "==================================================="
          ForEach($app in $mfSrv.Applications) {
             $myApp = $App.AppName
             Write-Host "Removing App [$myApp] from Server [$Server]"
             $app.RemoveServer($Server)
             $app.SaveData()
          }
       }
       else {
         Write-Host "No Published Applications for $Server"
      }
    }

    Notes: Pretty strait forward. Remember that it removes ALL apps from the server.

    Name: Remove-CitrixApplication
    Purpose: Removes Citrix App from Server

    function Remove-CitrixApplication {
       Param([string]$Srv,[string]$myapp)
       Write-Host
       $AppRemoved = $false
       $Srv = $Srv.toUpper()
       $mfSrv = New-Object -ComObject MetaFrameCOM.MetaFrameServer
       $mfSrv.Initialize(6,"$Srv")
       If($mfSrv.Applications.Count -gt 0) {
          ForEach($app in $mfSrv.Applications) {
             If($app.AppName -eq "$myapp") {
                Write-Host "Removing App [$myApp] from Server [$Srv]"
                $app.RemoveServer($Srv)
                $app.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
       }
    }

    Notes: Only removes specified app.

    Coming Soon: Should be posting more complex Citrix scripts to Add/Remove apps. Get some basic Stats, and such.

    Appendix:
    ———
    Typical:
    MetaFrameCOM.MetaFrameServer: (6,”SERVERNAME”)
    MetaFrameCOM.MetaFrameAppSrvBinding: (6,”SERVERNAME”,”AppPath\AppName”)
    MetaFrameCOM.MetaFrameApplication: (3,”AppPath\AppName”)
    MetaFrameCOM.MetaFrameZone: (”ZONE”)
    —— Will be adding headers soon —–