Tag Archives: Administration

App-v 4.6 large cache detection and alert script

App-v 4.6 generates sometime a large sftfs.fsd file. Users are experiencing performance issues when the file exceeds 15 GB. To fix this problem administrators remove the file by rebooting the Xenapp in safe mode. No user should be logged in of course. The Citrix servers therefore need to be empty. My colleagues ran a VB script that did the job of detecting but nothing else. I have made a script in Powershell that does the same but also set the server in ProhibitLogOns logon mode. The implement this script the server must have the Xenapp SDK installed. The server which runs the script need to have relay rights on the SMTP server. You’ll also need to have a Xenapp Server with PSRemoting enabled or the server itself should be member of the Xenapp farm.

#Load Modules and Snapins
Import-Module ActiveDirectory
Add-PSSnapin Citrix*
#Opens array for servers that apply on the conditions specified below
$ProhibitLogonServers =@()
$SMTP = "SMTPSERVER"
#Get all Xenapp server based on OU
$list = Get-ADComputer -Filter * -SearchBase "OU=Citrix Servers,OU=Servers,DC=Domain,DC=LOCAL"
Foreach ($Computer in $List){$Server = $Computer.DNSHostName 
$Result = GI "\\$Server\d$\AppVClientcache\Global\SoftGrid Client\sftfs.fsd"
If ($result.length -ne 0){$Outinfo = $Server+";"+$Result.length+" Bytes"
AC -Value $Outinfo -Path ".\log.csv"
#The conditions are met if the file is greater and engels 15 GB
If (($Result.length / 1073741824 ) -ge 15){$ProhibitLogonServers += $Computer}}}

#Mail message if no server are found                                                                                                                                                                            
If ($ProhibitLogonServers.length -eq 0){
$Mail = "Er is geen server gevonden die voldoet aan de voorwaarden om op ProhibitLogons te worden gezet" + "`n`n" + "Met Vriendelijke groet," +  "`n" + "Ochtend Controle Script"
Send-MailMessage -From "SendingAddress" -Subject "Prohibitlogons Report" -To "DestinationAddress" -Body $mail -SmtpServer $SMTP}

#Mail message one server is found
If ($ProhibitLogonServers.count -eq 1){
$Mail = "Er is een server gevonden die voldoet aan de voorwaarden om op ProhibitLogons te worden gezet. De server heeft als naam:" + "`n`n" + "$ProhibitlogonServers.Name" +"`n`n"+ "Met vriendelijke groet," + "`n`n" + "Ochtendcontrole Script"
Send-MailMessage -From "SendingAddress" -Subject "Prohibitlogons Report" -To "DestinationAddress" -Body $mail -SmtpServer $SMTP}

#Mail message if more than one server is found
If ($ProhibitLogonServers.count -gt 1){
$Mail = "Er zijn servers gevonden die voldoen aan de voorwaarden om op ProhibitLogons te worden gezet. De servers hebben de naam:" + "`n`n"
foreach ($server in $prohibitlogonservers){$Mail = $Mail + $Server.Name + "`r`n"}
$Mail = $Mail  + "`n" + "Met vriendelijke groet," + "`n`n" + "Ochtend Controle Script"
Send-MailMessage -From "SendingAddress" -Subject "Prohibitlogons Report" -To "DestinationAddress" -Body $mail -SmtpServer $SMTP}

#Opens array to check if the server has been set to prohibit logons. If servers are found a retry will be executed
$FailedServers = @()
If ($Prohibitlogonservers -ne $null){
Foreach ($Server in $Prohibitlogonservers){Set-XAServerLogOnMode -ServerName $Server.Name -LogOnMode Prohibitlogons -ComputerName CitrixServer}
Foreach ($Server in $Prohibitlogonservers){If ((Get-XAServer -ServerName $Server.Name -ComputerName CitrixServer).LogonMode -notmatch "ProhibitLogonOns"){$FailedServers += $Server}}}

#If a server is rebooting during the process you do want to make sure that server is set to Prohibit Logons. 
Start-sleep -s 900

#Retry setting the server to prohibit logons
If ($Failedservers.count -ne 0){
Foreach ($Server in $FailedServers){Set-XAServerLogOnMode -ServerName $Server.Name -LogOnMode Prohibitlogons -ComputerName CitrixServer}
$Failedservers = @()
Foreach ($Server in $FailedServers){If ((Get-XAServer -ServerName $Server.Name -ComputerName CitrixServer).LogonMode -notmatch "ProhibitLogonOns"){$FailedServers += $Server}}}

#Mail message if servers aren't set to prohibit logons
If ($Failedservers.count -ne 0){$Mail = "Er zijn servers niet succesvol op prohibitlogons gezet. Deze servers zijn:" + "`n`n"
foreach ($server in $Failedservers){$Mail = $Mail + $Server.Name + "`r`n"}
$Mail = $Mail  + "`n" +'Zet deze server(s) handmatig op ProhibitLogons' + "`n`n" + "Met vriendelijke groet," + "`n`n" + "Ochtend Controle Script"
Send-MailMessage -From "SendingAddress" -Subject "Prohibitlogons Failed" -To "DestinationAddress" -Body $mail -SmtpServer $SMTP}

 

Get uptime server

To get the uptime of a server in powershell run the following command:

Get-CimInstance -ClassName win32_operatingsystem | select csname, lastbootuptime

This works for Powershell v3 and v4. To run it against a remote server run the following code:

Get-CimInstance -ClassName win32_operatingsystem -ComputerName SERVERNAME| select csname, lastbootuptime

Please note that this only works if PS Remoting is enabled. To enable psremoting enter the command:

Enable-PSRemoting -Force

You can also type the command:

net statistics Workstation

Multiple levels GCI

In Powershell version 3 there is no way to limit the number of levels to do something in directories/registry/AD and so forth. So if you want to do that you can use a script like this:

[int]$Levels = "4"
$Directory = "\\Directory"
$List = GCI $Directory | Where {$_.psiscontainer -eq $true}
$Templist = $list
$temp = @()
While ($levels -ne 1){
Foreach ($item in $Templist){
$Temp += gci $item.Fullname | Where {$_.PSisContainer -eq $True}}
$list += $Temp
$Templist = $Temp
$Levels--
$Temp =@()}
$list = $list | Sort-Object Fullname
Foreach ($line in $List){Add-content -value $Line.Fullname -path ".\Export.csv"}

App-v Staging script

We are having performance issues with some virtualising some packages in App-v 5. There’s a package that needs it’s entire registry before it performs. When a user runs the starts the packages 3 to 4 times it starts to perform better. We have seen that the registry increased by 200 MB of plain text size. This is quit a lot. Since all our Xenapp servers are virtualised and provisioned will every reboot remove the changes in the registry. So the package remains very slow. We created a script that runs on all servers and starts a CMD.exe in a virtualised process. This will trigger staging and thus loading the entire registry. When this script is ran before any users connect, users will always have a good performance.

#import AppVClient module
ipmo *appv*

#get all appv packages
$apps = Get-AppvClientPackage
$obj = @()

#Get connection group applications
$connApps = (Get-AppvClientConnectionGroup).GetPackages()

#remove connecion-group applications from the list
[System.Collections.ArrayList]$appList = $apps
foreach ($connApp in $connApps) {$appList = $appList | ? { $_.name -ne $connApp.Name }}


#for each (non-connection group) appv package...
foreach ($app in $appList) {
$prop = New-Object System.Object

#get each appv package ID and version ID
$package = ($app.packageID).ToString()
$version = ($app.versionID).ToString()

#start a blank cmd.exe in the environment (this kicks off the AppV5 registry staging)
Start-AppvVirtualProcess -AppvClientObject (Get-AppvClientPackage $app.name) cmd.exe


    do {
    write-host "Testing: " $app.name
        sleep 1
       }
       until (Test-Path "HKLM:\SOFTWARE\Microsoft\AppV\Client\Packages\$package\Versions\$version\RegistryStagingFinished")
       write-host $app.name
       $appvProcess = get-appvVirtualProcess
       stop-process $appvProcess -force
    }

#for each connection group package...
$conGroups = Get-AppvClientConnectionGroup
foreach ($group in $conGroups) {
$prop = New-Object System.Object

#get each connection group package ID and version ID
$package = ($group.groupID).ToString()
$version = ($group.versionID).ToString()

#start a blank cmd.exe in the environment (this kicks off the AppV5 registry staging)
Start-AppvVirtualProcess -AppvClientObject ($group) cmd.exe


    do {
    write-host "Testing: " $group.name
        sleep 1
       }
       until (Test-Path "HKLM:\SOFTWARE\Microsoft\AppV\Client\PackageGroups\$package\Versions\$version\RegistryStagingFinished")
       write-host $group.name
       $appvProcess = get-appvVirtualProcess
       stop-process $appvProcess -force
    }
    

In order to start the above script I used DPAPI to encrypt the password of the service account. I configured a scheduled task to run every day at 6:30 AM. I wrote a article or two about his some time ago. It is important to encrypt the password when logged in to that account. The profile of that service account should not be removed!

The script used to start the script above is shown below

<#Custom Module#>
Import-Module "LocationWhenTheFunctionIsLocated\Function.psm1" -force
<#Script Start#>
Add-PSSnapin Citrix*
#Note that the username is without the domain name
$UserName = "ServiceAccount"
$SecureString = Get-Content -Path "LocationOfTheStoredPassword.txt" | ConvertTo-SecureString -Entropy ([Math]::PI)
$SecureString = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($SecureString))
#If the server that launch the script is not a Citrix server you have to add -ComputerName COMPUTERNAME
$OnlineServers = Get-XAServer -ZoneName FarmName -OnlineOnly  | ?{$_.ServerName -match "PrefixFromProductionServers" -and $_.ServerName -notmatch "tst" -and $_.ServerName -notmatch "DEV"}
#Line below is for testing purposes
Add-content -Value $OnlineServers -Path "G:\Scheduler_Scripts\GMN\App-V Prelaunch\Test.csv"
Foreach ($Server in $Onlineservers){

$ServerName = $Server.ServerName
$Date = (Get-Date).AddMinutes(30)
schtasks /create /S $ServerName /RU DOMAIN\$Username /RP $SecureString /SC Once /SD $date.ToString('dd"/"MM"/"yyy') /ST $date.ToString('HH":"mm') /TN "App-V 5 Prelaunch Script" /TR "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe \\SharedLocationOfScriptAbove.ps1" /F 
}

A few things are important to make this work. First you need to share the first script on a shared location, let’s say the netlogon or whatever directory works for you. Next you’ll need to have the Xenapp SDK installed on the server that runs the script. This is needed. Only the Powershell CMDlets are required. You need to have a Xenapp server that can be reached over the network. You’ll also need to know the farm name. And of course you’ll need the have the function loaded. I added the function below this post. Check you Execution policy settings before scheduling.

Function

Find members of a local group

We needed a list of all local admins in the domain. I found some code somewhere and adjusted it to my personal needs.

Import-Module ActiveDirectory

$date = (get-date).AddDays(-35)
$list = (Get-ADComputer -Filter {(OperatingSystem -like "*Server*") -and (PasswordLastSet -ge $Date)} ).Name

$Result = @()

foreach($server in $list){

$computer = [ADSI](”WinNT://” + $server + “,computer”)
$Group = $computer.psbase.children.find(”Administrators”)

function getAdmins

{$members = $Group.psbase.invoke(”Members”) | %{$_.GetType().InvokeMember(”Adspath”, ‘GetProperty’, $null, $_, $null)}
$members}

$Result += $server
$Result += ( getAdmins )
$Result += " "
}
$Result | ac c:\LocalAdmins.csv

Script to check when a XenApp server is set to prohibit logons

It is hard to find out who set a server to Prohibit logons in XenApp. I had some spare time to make a oneliner for the service desk to search the eventlog for any records matching the specifics. The Logon process is run by the ImaService. When you know when to server is set to prohibit logon, you may find the administrator.

$List2 =@() ;$load = (qfarm /load) ;write-host $Load[1];Write-Host $load[2]; Foreach ($item in $load){If ($Item -match "ProhibitLogons"){Write-host $item;$Item2 = $item -split " " ;$item2 = $item2[0] ; $List2 += $Item2}} ;$Date = (Get-Date).AddDays(-1) ; Foreach ($item in $list2){Get-EventLog -LogName Application -after $Date -ComputerName $item -InstanceId 1073751835 | ? {$_.Message -match "Prohibit"} | Select TimeGenerated,MachineName}

Or if you feel more comfortable having a script you can do the following:

$List2 =@() 
$load = (qfarm /load) 
write-host $Load[1]
Write-Host $load[2]
Foreach ($item in $load){If ($Item -match "ProhibitLogons"){
Write-host $item
$Item2 = $item -split " " 
$item2 = $item2[0] 
$List2 += $Item2}} 
$Date = (Get-Date).AddDays(-1) 
Foreach ($item in $list2){
Get-EventLog -LogName Application -after $Date -ComputerName $item -InstanceId 1073751835 | ? {$_.Message -match "Prohibit"} | Select TimeGenerated,MachineName}

This “Script” runs a qfarm /load. Next presenting the users with a table from qfarm. It only lists server that are set to prohibitlogons. It splits the name from the rest and adds that to a array. The date is queried minus one day. You can change that to more or less. Next the Eventlog of the XenApp server is queried for a matching record. You can also set $_.EventID -match “10011” for the matching eventID. At the last step there’s a select-object for the TimeGenerated and MachineName

Export all TNSNames files from all servers in the domain.

A colleague asked me if it was possible to export all servers that have an Oracle directory. I made a script that collects all server from Active Directory. Next I test if the C:\Oracle Directory Exists. That is arrayed. Next I search the directories of tnsnames. Excluding the .sample files of course.

Import-Module ActiveDirectory
$ADComputers = Get-ADComputer -Filter * -Properties * | Where {$_.OperatingSystem -match "Server"}
$Computers =@()
ForEach ($ADComputer in $ADComputers){$ADComputer = $ADComputer.DNSHostName
If((Test-Path "\\$ADComputer\C$\Oracle") -eq $True){$Computers += $ADComputer}}

ForEach ($Computer in $Computers){$Folders =@( gci "\\$Computer\c$\Oracle\" -recurse)
ForEach ($Item in $Folders){if ($item -match "TNSNames.ora" -and $item.fullname -notmatch "sample"){Add-content -Value $item.fullname -Path ".\Export.csv"}}}

Office 2010,2013 activation problem

For a customer I prepared an image. This image contained  a 32 bit version of Office 2010. I needed to enter a key for the installation. At that point I did not had access to their  volume licensing. I wanted to complete the image as soon as possible. That is why I entered a MSDN key. I was unaware at that point that this would cause many problems. First of all the deployed image at that point didn’t accept MAK keys. When I entered the MAK key I received an error that the key was incorrect. After an hour of troubleshooting I downloaded the VL version of Office 2010. I clicked on install. After install Office 2010 accepted my key. However it didn’t activate. I had to call Microsoft Activiation Center in order to activate the key. I asked them why automatic activation did not work. They said that I had to contact MSDN Activation Support. I put the two together and knew that this was caused by the MSDN key that somewhere was activated. I looked for an activation key in the registry somewhere. After deleting some keys I could activate. The keys are:

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Office\14.0\Registration

And if you installed the 32 bit Office 2010:

HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Office\14.0\Registration

Do this before you install Office 2010 VL.

Find old share names in files on Apps share

A client was talking about how he couldn’t quit sharing a certain share in his Citrix environment. There were to many connections still to that share. So I suggested that I could help him by making a script that looks for certain values in .ini, .log, .cmd, .bat, .cfg, .config etc files. You can adjust this to your own preference.

I did a check of the drive mapping G:\. Please note that a lot of log files contain Logging: which matches G:. That is why I did a match on G:\. In Powershell the ‘\’ character  is an expression followed by a special character which in this case will be \. That is why you need a double slash.

$Locatie = "\\ShareName" 
$Exportfile = ".\Exportfile.csv"
$List = gci $Locatie -Recurse | Where {$_.Extension -match ".ini" -or $_.Extension -match ".cfg" -or $_.Extension -match ".config" -or $_.Extension -match ".inc" -or $_.Extension -match ".bat" -or $_.Extension -match ".cmd" -or $_.Extension -match ".log" -or $_.Extension -match ".txt"}
Foreach ($item in $list){$Content = Get-Content $item.fullname
$Pad = $item.fullname
If ($Content -match "G:\\"){Add-Content -Value  "Er is een verwijzing naar G:\ gevonden in: $Pad" -Path $Exportfile}
If ($Content -match "G:/"){Add-Content -Value  "Er is een verwijzing naar G:/ gevonden in: $Pad" -Path $Exportfile}
If ($Content -match "ServerName1"){Add-Content -Value  "Er is een verwijzing naar ServerName1 gevonden in: $Pad" -Path $Exportfile}
If ($Content -match "ServerName2"){Add-Content -Value  "Er is een verwijzing naar ServerName2 gevonden in: $Pad" -Path $Exportfile}
If ($Content -match "ServerName3"){Add-Content -Value  "Er is een verwijzing naar ServerName3 gevonden in: $Pad" -Path $Exportfile}
If ($Content -match "ServerName4"){Add-Content -Value  "Er is een verwijzing naar ServerName4 gevonden in: $Pad" -Path $Exportfile}
If ($Content -match "ServerName5"){Add-Content -Value  "Er is een verwijzing naar ServerName5 gevonden in: $Pad" -Path $Exportfile}
If ($Content -match "ServerName6"){Add-Content -Value  "Er is een verwijzing naar ServerName6 gevonden in: $Pad" -Path $Exportfile}}

Get file size of old files on a fileserver

So I was at a costumer. They wanted an export of the file size that files older than 7 years populate on the file server. I made a very simple script that does the job.

$Locatie = "D:\"
#Verander het cijfer om het aantal jaar aan te passen
$Datum = (Get-Date).AddYears(-7)
$Outfile = ".\Output.txt"
$List = @(GCI $Locatie -Recurse | Where {$_.LastWriteTime -lt $Datum})
$Outinfo = "De lijst bevat "+ $list.count +" bestanden"
Add-Content -Path $Outfile -Value $Outinfo
$Bytes = 0
ForEach ($Item in $List){($Bytes=$Bytes + [int]$Item.length)}
$Outinfo = "De Bestanden bevatten $Bytes bytes"
Add-Content -Path $Outfile -Value $Outinfo
$Outinfo = "Dat is "+ $Bytes/1048576 +" MegaBytes"
Add-Content -Path $Outfile -Value $Outinfo