Archive for the 'Scripting' Category
August 27th, 2012 by Michael
After an unpretty Hyper-V cluster failover, several machines in our Xen Desktop deployment were showing an “unknown” power state. After a call to Citrix, they gave my coworker a few commands to use to fix it.
This has to be done from the Xen Desktop controller:
Load the Citrix PSSnapIn:
Add-PSSnapIn Citrix.*
This gets information from VMM about all of the VMs in VMM:
Cd XDHyp:\
Get-ChildItem -recurse | Out-File –Filepath c:\xdhyp.txt
This command gets all of the machines that are PowerState Unknown in Xen Destkop:
Get-BrokerMachine -PowerState Unknown
The problem is that the “Id” from the first command doesn’t match the “HostedMachineId” from the second command. To fix this, you run this command with the correct domain and machine name from the second command and the “Id” from the first command:
Set-BrokerMachine -MachineName <MyDomain\MyMachine> -HostedMachineId <Id>
You have a lot of machines where this is a problem, it could take a while to go through and match these up. To save some time with the 75 or so we had to do, created this script to do it:
#Add-PSSnapIn Citrix.*
#$ErrorActionPreference=Continue
$x = 0
$UnknownList = Get-BrokerMachine -PowerState Unknown
# HostedMachineId : 51c7f7a2-64bf-481a-86fd-49b9a3fbf993
# MachineName : Domain\MachineName
foreach ($_ in $UnknownList)
{
$UnknownMachine = $_
Write-Host $_.MachineName
$UnknownMachineName = $_.MachineName
#trim the domain to search
$SearchName = $UnknownMachineName.TrimStart("<domain>\")
Write-Host "Search Name is $SearchName"
$Group = "XDHyp:\Connections\<VMMSERVER>\<Vmmhostgroupname>.hostgroup\<clustername>.cluster"
$GroupList = Get-ChildItem $VDCB | Where-Object {$_.Name -match $SearchName}
# Name : MachineName
# Id : 8d9d4e54-d374-406b-b4e3-7dcd2f47e7a9
foreach ($_ in $GroupList)
{
$x ++
Write-Host $_.Name
$HostedMachineId = $_.Id
Write-Host $HostedMachineId
}
Write-Host $x
set-BrokerMachine -MachineName $UnknownMachineName -HostedMachineId $HostedMachineId
}
July 17th, 2012 by Michael
We have some old email database backup files that we extracted messages from. The purpose of this was to be able to expire the backups and do away with them, while keeping the messages in our Journal for e-discovery purposes. There are better ways to do what we did, that the way we did this, but it has been a process of learning, and one of the things I was able to learn is how to import .msg files into Outlook.
You have to have a machine that has Outlook installed. Outlook 2007 is the version I used. This would work with Outlook 2010, but you will get a popup about allowing scripting access to Outlook.
First, create the connection to Outlook:
$outlook = New-Object -comobject outlook.application
$namespace = $outlook.GetNamespace("MAPI")
Then connect to the folder, such as the Inbox:
$objInbox = $outlook.Session.GetDefaultFolder(6)
Other examples:
$olAppointmentItem = 1
$olFolderDeletedItems = 3
$olFolderOutbox = 4
$olFolderSentMail = 5
$olFolderInbox = 6
$olFolderCalendar = 9
$olFolderContacts = 10
$olFolderJournal = 11
$olFolderNotes = 12
$olFolderTasks = 13
$olFolderDrafts = 16
$objDraftFolder = $outlook.Session.GetDefaultFolder($olFolderDrafts)
$objDeletedFolder = $outlook.Session.GetDefaultFolder($olFolderDeletedItems)
I like to know how many messages are in the folder before I begin the import:
$colItems = $objDraftFolder.Items #this gets the items in the folder
$FolderItemCount = $colItems.Count #this counts them
Write-Host $FolderItemCount
Now you have to open the item and then move it to the folder you want to save it in:
$olMailItem = $NameSpace.OpenSharedItem($MailItem)
$olMailItem.Move( $objDraftFolder )
If you put the above lines in, you will get a lot of data on the screen about the email. To prevent that while still accomplishing the goal of moving the message to Outlook, simply put [void] in front like this:
[void]$olMailItem.Move( $objDraftFolder )
I am working with around a million files, so this was a rather involved script to create. Here is the script I used:
$olMailItemPath = "F:\Sorted\MoveToOutlook\ByThousands\*"
$AfterTime = "12/21/2007"
$olAppointmentItem = 1
$olFolderDeletedItems = 3
$olFolderOutbox = 4
$olFolderSentMail = 5
$olFolderInbox = 6
$olFolderCalendar = 9
$olFolderContacts = 10
$olFolderJournal = 11
$olFolderNotes = 12
$olFolderTasks = 13
$olFolderDrafts = 16
Write-Host $olMailItemPath
$x=0
$SourceFolders = Get-Item $olMailItemPath
echo $SourceFolders.count
$outlook = New-Object -comobject outlook.application
$namespace = $outlook.GetNamespace("MAPI")
foreach ($_ in $SourceFolders)
{
$SourceFolder = $_
Write-Host "SourceFolder is $SourceFolder"
$SourceFiles = Get-ChildItem -path $SourceFolder -recurse -include *.msg
$SFCount = $SourceFiles.count
Write-Host "Source File Count is $SFCount"
$objDraftFolder = $outlook.Session.GetDefaultFolder($olFolderDrafts)
$objDeletedFolder = $outlook.Session.GetDefaultFolder($olFolderDeletedItems)
$colItems = $objDraftFolder.Items
$FolderItemCount = $colItems.Count
IF ($FolderItemCount -ge 10000)
{
Write-Host "Draft Folder Item Count is $FolderItemCount"
Write-Host "Sleeping…"
sleep -s 300
}
foreach ($_ in $SourceFiles)
{
$x ++
# Write-Host $x
$MailItem = $_
# Write-Host "Mail Item is $MailItem"
$olMailItem = $NameSpace.OpenSharedItem($MailItem)
$DateRecieved = $olMailItem.ReceivedTime
# Write-Host "Date Recieved is $DateRecieved"
If ($DateRecieved -le $AfterTime)
{
# Write-Host "Bad Date $DateRecieved"
[void]$olMailItem.Move( $objDeletedFolder )
}
else
{
# Write-Host "Moving $MailItem"
[void]$olMailItem.Move( $objDraftFolder )
}
# Write-Host "Removing $MailItem"
Remove-Item $MailItem
}
}
December 28th, 2011 by Michael
We use Citrix for a lot of applications, and I have a need to launch Outlook, then an application, and then close Outlook when that application is closed by the user. This seems like a pretty simple thing to do (and I suppose it is, sort of) but it took me a while to figure it out.
One piece of the puzzle is that PowerShell remains open if you do it the way I have it setup right now. If the user closes that PowerShell window, then the monitor process will not close Outlook when the user exits the LOB app. In order to mitigate this issue somewhat, I wanted to start PowerShell minimized. The way to do this is:
powershell -WindowStyle Minimized .\ScriptToRun.ps1
July 29th, 2010 by Michael
If you want to know what logon script users are getting, this is an easy way to get that information:
Import-Module -Name ActiveDirectory
Get-ADUser -Filter * -SearchBase "OU=YourOUName,DC=YourDomain,DC=COM" -properties ScriptPath | Export-Csv "c:\script\ADUser.csv"
Note: In order for this to work, you have to have the ActiveDirectory Module loaded.
March 25th, 2010 by Michael
I have been working on a simple little script to copy a file and then launch a program. I am sure that there are a lot of ways to do it, but I decided to use PowerShell, and this is what I came up with:
$CheckForFile = "H:\custom.ini"
$FileToCopy = "c:\IT\custom.ini"
$CopyFileTo = "H:\"
$PathTest = Test-Path $CheckForFile
If ($PathTest -eq "false")
{
Copy-Item $FileToCopy $CopyFileTo
}
#uses the Invoke-Item command to launch the application
Invoke-Item "C:\Program Files\executable to launch.exe"
This is for use in a Citrix/Terminal Server environment, so I want to be able to call this script like this: PowerShell copythenlaunch.ps1
When I tested that, I got this:
C:\IT>powershell copythenlaunch.ps1
The term ‘copythenlaunch.ps1′ is not recognized as a cmdlet, function, operable
program, or script file. Verify the term and try again.
At line:1 char:18
+ copythenlaunch.ps1 <<<<
I kept thinking there was some problem with the install of PowerShell (I am running this particular script on a Windows 2003 Server) or that I had some illegal character in the name (it had a number in it originally) or some other simple problem. Finally I did a search and came across this little bit of conversation:
re: Power and Pith
I just started with PowerShell.
Wanted to run some test scripts from you download.
When I tpye in Beep.ps1 I get "The term ‘Beep.1′ is not recognized….."
What Am I doing wrong?
Friday, December 29, 2006 3:17 PM by MikeL
# re: Power and Pith
> When I tpye in Beep.ps1 I get "The term ‘Beep.1′ is not recognized….."
> What Am I doing wrong?
You are relying upon a traditional bad shell behaviour that has been a security nightmere for decades.
In PowerShell, you have to be explicit if you want to run a command in the current directory. Type ".\beep.ps1"
Jeffrey Snover [MSFT]
Windows PowerShell/MMC Architect
Visit the Windows PowerShell Team blog at: http://blogs.msdn.com/PowerShell
Visit the Windows PowerShell ScriptCenter at: http://www.microsoft.com/technet/scriptcenter/hubs/msh.mspx
Friday, December 29, 2006 5:19 PM by PowerShellTeam
# re: Power and Pith
Thank You for supplying the ".\*" information. I have been racking my brain for almost two days wondering what I was doing wrong. And to think it was as simple as using the PROPER .\yourscripthere.ps1 format.
Thank you very very much
Ditto on the thanks…
Windows PowerShell Blog : Power and Pith
March 4th, 2010 by Michael
I seem to run into an issue when I run some PowerShell scripts where I get prompted at each line of the script for confirmation. That can get really annoying, so I have to look up how to prevent that behavior. Thankfully, there is already some good information out there on how to do that:
When confirmation is turned on by $ConfirmPreference, you can turn it off for any individual cmdlet invocation using "-Confirm:$false". You can also use "-Confirm:$false" to turn off default confirmation for high impact cmdlets such as Removing a Mailbox. Another way to turn off confirmation is by setting $ConfirmPreference to "None"; you can limit the effect by setting $script:ConfirmPreference etc, see "get-help about_scope" for more details.
For more details and options besides just turning it off, go see the original post:
Windows PowerShell Blog : ConfirmPreference
February 24th, 2010 by Michael
At some point, I had a desire to list all the computer accounts for any server OS in Active Directory. I am pretty sure that I did a search and found the script below, but I don’t remember where, so whoever wrote it doesn’t get credit this time…
$strCategory = “computer”
$strOperatingSystem = “Windows*Server*”
$objDomain = New-Object System.DirectoryServices.DirectoryEntry
$objSearcher = New-Object System.DirectoryServices.DirectorySearcher
$objSearcher.SearchRoot = $objDomain
$objSearcher.Filter = (“OperatingSystem=$strOperatingSystem”)
$colProplist = “name”
foreach ($i in $colPropList){$objSearcher.PropertiesToLoad.Add($i)}
$colResults = $objSearcher.FindAll()
Write-Host $colResults.count
foreach ($objResult in $colResults)
{
$objComputer = $objResult.Properties;
$objComputer.name
}
If you change the $strOperatingSystem = “Windows*Server*” to something like $strOperatingSystem = “Windows*” it will return all computer accounts that have “Windows” in the Name field on the Operating System tab of the properties of the AD object:

September 22nd, 2009 by Michael
I have written a lot of scripts that use .txt files to read or store data, but I have a need to read some information from an .xml file. This could be done by treating the file as a simple txt file, but it would require some pretty good filtering that is already a part of the xml file. A quick search helped me locate this article:
http://blogs.msdn.com/kalleb/archive/2008/07/19/using-powershell-to-read-xml-files.aspx
Which contained the key to helping me with what I needed to do. The specific piece I needed was in Lesson 2:
Lesson 2:
Read data from an XML-file.
The XML-file that I’m going to read from has the following structure:
<Users>
<User>
<Name>Kalle</Name>
</User>
<User>
<Name>Becker</Name>
</User>
</Users>
Reading data from an XML-file is really easy in PowerShell! Use this command to load the file into an variable:
PS C:\Tmp> [xml]$userfile = Get-Content Accounts.xml
When the xml-file is loaded you can type “$userfile.U” and press tab to get auto completion!! It’s a breeze.
The trick is that you have to actually READ what is in front of you. The key here is to let PowerShell know that you are reading an xml file, and that is done by placing [xml] prior to getting the content. I missed that the first six times I read this and couldn’t figure out why I wasn’t getting the results I expected.
September 4th, 2009 by Michael
One of the things that we spend a lot of time on is trying to keep track of what servers have enough free space. We have a lot of different tools to check drive space, and we even use some of them from time to time. We have a pretty complicated system created by Rickey that creates a nice webpage, with highlighting for problem areas (percentage change from day to day, current percent free, etc.) It even puts the info into a database for historical reporting.
We don’t store or report on VMs currently, mainly because we were trying to keep track of total REAL disk used. VMs often don’t use as much as they think they do, so that would skew the results, as well as the fact that we are reporting on the hosts.
All of that is the reason that Patrick asked me to come up with some other tool to use for the VMs so I happened to find a few pieces of PowerShell script that I managed to put together to do a pretty good job of providing some of the info we wanted, and I thought I would share that with the 2 people who read my blog.
$servers = Get-Content servers.txt
#Open Excel and create a new workbook and worksheet
$ExcelSheet=New-Object -comobject Excel.application
$WorkBook=$ExcelSheet.WorkBooks.add(1)
$WorkSheet=$WorkBook.WorkSheets.item(1)
#Header row
$WorkSheet.cells.item(1,1)=”Computer Name”
$WorkSheet.cells.item(1,2)=”Disk Device ID”
$WorkSheet.cells.item(1,3)=”Volume Name”
$WorkSheet.cells.item(1,4)=”Size (GB)”
$WorkSheet.cells.item(1,5)=”Free Space (GB)”
$WorkSheet.cells.item(1,6)=”Space Used (GB)”
$WorkSheet.cells.item(1,7)=”Percent Used”
$i=2
ForEach ($ComputerName in $servers)
{
echo "Server Name : ", $ComputerName
$Disks = gwmi –computername $ComputerName win32_logicaldisk -filter "drivetype=3"
foreach ($Disk in $Disks)
{
$Size = "{0:0.0}" -f ($Disk.Size/1GB)
$FreeSpace = "{0:0.0}" -f ($Disk.FreeSpace/1GB)
$Used = ([int64]$Disk.size – [int64]$Disk.freespace)
$SpaceUsed = "{0:0.0}" -f ($Used/1GB)
$Percent = ($Used * 100.0)/$Disk.Size
$Percent = "{0:N0}" -f $Percent
$WorkSheet.cells.item($i,1)=$ComputerName
$WorkSheet.cells.item($i,2)=$Disk.deviceid
$WorkSheet.cells.item($i,3)=$Disk.volumename
$WorkSheet.cells.item($i,4)=$Size
$WorkSheet.cells.item($i,5)=$FreeSpace
$WorkSheet.cells.item($i,6)=$SpaceUsed
$WorkSheet.cells.item($i,7)=$Percent
$i=$i+1
}
}
#Show the results
$ExcelSheet.visible=$true
March 11th, 2009 by Michael
I have the unfortunate need to occasionally fix things in the registry, mostly related to applications running in a Citrix environment. I believe there are nice tools out there that are supposed to do this for you, if given the right information, but I haven’t bothered to figure out what those tools are or how to use them. Instead, I torture myself with trying to muddle through VBScript and make it do what I want.
In that spirit, I am going to show you one of my pitiful scripts (and this one is horrible, because I didn’t bother to clean it up after I worked out how to make it accomplish the desired goal. So… here you go:
‘==========================================================================
‘
‘ VBScript Source File — Created with SAPIEN Technologies PrimalScript 2007
‘
‘ NAME: BorlandDBPathFix.vbs
‘
‘ AUTHOR: Michael Phillips , Brasfield & Gorrie, LLC
‘ DATE : March 11, 2009
‘
‘ COMMENT: This script is to change the value of 2 registry keys on a per
‘ user basis. It should set the value to be the correct system
‘ drive (c: or u:).
‘ It should also be noted that this script isn’t very pretty.
‘==========================================================================
‘Registry stuff
Const HKCU = &H80000001 ‘This defines the Current User Hive
Const HKLM = &H80000002 ‘This defines the Local Machine Hive
Const REG_SZ = 1
Const REG_EXPAND_SZ = 2
Const REG_BINARY = 3
Const REG_DWORD = 4
Const REG_MULTI_SZ = 7
vDebug = 1
Dim strComputer
strComputer = “.” ‘This computer is the “.”. If you want another computer, replace the .
Set oReg=GetObject(“winmgmts:{impersonationLevel=impersonate}!\\” & _
strComputer & “\root\default:StdRegProv”)
sGetPath
Sub sGetPath
Set oShell = CreateObject( “WScript.Shell” )
strSystemDrive = oShell.ExpandEnvironmentStrings(“%systemdrive%”)
‘* wscript.echo strSystemDrive
fGetRegistryValues strSystemDrive
End Sub
‘*
‘* This is what we are looking for:
‘* [HKCU\Software\Borland\BDS\4.0\DBExpress]
‘* @=”"
‘* “Connection Registry File”=”C:\\Program Files\\Common Files\\Borland Shared\\DBExpress\\dbxconnections.ini”
‘* “Driver Registry File”=”C:\\Program Files\\Common Files\\Borland Shared\\DBExpress\\dbxdrivers.ini”
Function fGetRegistryValues (vSystemDrive)
‘* wscript.echo vSystemDrive & ” is being passed as vSystemDrive”
oReg.GetExpandedStringValue HKCU,”Software\Borland\BDS\4.0\DBExpress”,”Connection Registry File”,strValue1
oReg.GetExpandedStringValue HKCU,”Software\Borland\BDS\4.0\DBExpress”,”Driver Registry File”,strValue2
strDriveLetter1 = Left (strValue1,2)
‘* wscript.echo strDriveLetter1 & strValue1 & strSystemDrive
strDriveLetter2 = Left (strValue2,2)
If strDriveLetter1 = vSystemDrive Then
If strDriveLetter2 = vSystemDrive Then
Else
fSetRegistryValues strValue1,strValue2,vSystemDrive
End If
Else
fSetRegistryValues strValue1,strValue2,vSystemDrive
End If
End Function
Function fSetRegistryValues (fValue1,fValue2,fDriveLetter)
‘* wscript.echo “Bad Letter is being passed ” & fValue1 & fValue2 & fDriveLetter
vLength1 = (Len (fValue1))-2
vLength2 = (Len (fValue2))-2
‘* wscript.echo “–strValue1 & vLength1– >” & fValue1 & ” ” & vLength1
strPathNoLetter1 = Right (fValue1,vLength1)
strNewKeyValue1 = fDriveLetter & strPathNoLetter1
fWriteStringRegistryValues “Software\Borland\BDS\4.0\DBExpress”,”Connection Registry File”,strNewKeyValue1
‘* wscript.echo strNewKeyValue1
strPathNoLetter2 = Right (fValue2,vLength2)
strNewKeyValue2 = fDriveLetter & strPathNoLetter2
fWriteStringRegistryValues “Software\Borland\BDS\4.0\DBExpress”,”Driver Registry File”,strNewKeyValue2
‘* wscript.echo (fDriveLetter & (Right (fValue2,vLength2)))
End Function
Function fWriteStringRegistryValues (fvRegistryKeyPath,fvRegistryKeyName,fvRegistryKeyValue)
If vDebug = 1 Then
‘* wscript.echo “Begining Function -fWriteStringRegistryValues-.”
End If
‘ This function takes input to write string values to the registry. Key must already exist.
oReg.SetStringValue HKCU,fvRegistryKeyPath,fvRegistryKeyName,fvRegistryKeyValue
End Function