Tag Archives: Powershell

Get all installed hot fixes and software

An adminstrator wanted to know if a certain KB from Microsoft had been installed. He wanted to know a command that does the job. In a world with DOS you can simply run:

systeminfo

However he also wanted to see if a Xenapp Hotfix had been installed. This of course can’t be done with the command above. So I wrote some code that exports the uninstall information from the local server. The code export x64 and x86 software. If the server is not 64 bit you’ll get errors. That said you’ll probably don’t have Powershell v3 installed on such server.

$List1 = Get-ItemProperty HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*
$List1 += Get-ItemProperty HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*
$List1 = $List1 | Select DisplayName, DisplayVersion, Publisher, InstallDate

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

Search for specific text in files on a location

Sometimes a administrator is forced to do some cleaning up that should have been done a million years ago. When previous administrators have left you a nice bucket of history in your environment. A customer of mine used to have a few fileservers around that aren’t there anymore. In DNS those names are resolving to a new CIFS server. However that location is going to change again and now we are forced to clean up that old mess. Of course I’m not going to do the job of opening a few hundred thousand .ini, .config, .cfg and so forth myself. I created a script that does the job and can be delegated to someone else who has more time than me.

Clear
$Outputdir = \\Outputdirpath


$Location = Read-Host "Voer het volledige DFS pad in, in UNC Format bijvoorbeeld:\\DFSFolder"
$TestResult = Test-Path $Location
While ($Testresult -ne $true){
write-host "De opgegeven locatie bestaat niet. Voor opnieuw in" -b Black -f Red
$Location = Read-Host "Voer de DFS Locatie in in UNC Format bijvoorbeeld:\\DFSFolder"
$TestResult = Test-Path $Location}

$Extensions =@(Read-Host 'Voer een extensie in.De Extensie moet een . bevatten')
While ($Extensions -notmatch "."){$Extensions =@(Read-Host 'Voer een extensie in.De Extensie moet een . bevatten')}
$Choice = $null
While ($Choice -notmatch "[y|n]"){$Choice = read-host "Wilt u nog een Extensie toevoegen (Y/N)"
If ($Choice -match "Y"){While ($Extension -notmatch "."){$Extension =(Read-Host 'Voer een extensie in')}
$Extension += $Extension
$Choice = $Null}}

Remove-Variable Choice
$MatchList =@(Read-Host 'Voer een waarde in waaraan voldaan moet worden. Voor een drive mapping is een dubbele backslash vereist')
$Choice = $null
While ($Choice -notmatch "[y|n]"){$Choice = read-host "Wilt u waarde opgeven (Y/N)"
If ($Choice -match "Y"){$MatchItem =(Read-Host 'Voer een waarde in waaraan voldaan moet worden. Voor een drive mapping is een dubbele backslash vereist')
$MatchList += $MatchItem
$Choice = $null}}

$PathName = GI $Location
If ($PathName.Parent.Name -eq $Null){$ExportFile = "$OutputDir\ContentExport-"+ $PathName.Name + ".csv"}
If ($PathName.Parent.Name -ne $Null){$ExportFile = "$OutputDir\ContentExport-"+ $PathName.Parent.Name +"-"+ $PathName.Name +".csv"}

$List = gci $Location -Recurse | Where {$Extensions -match $_.Extension}
Foreach ($Item in $List){$Content = Get-Content $Item.fullname
$Pad = $item.fullname
Foreach ($Extension in $Extensions){If ($Content -match $Extension){Add-Content -Value  "Er is een verwijzing naar $Extension gevonden in: $Pad" -Path $Exportfile}}}

For those who do not speak Dutch very well. If you are searching for a drive mapping you should enter a double slash. The rest you’ll have to find out yourself 😉

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