Tuesday, March 13, 2012

AppV and Application Compatibility

I was having an issue with a old application that we want to run on our Citrix XenApp 6 farm; Microsoft Enterprise Reporting 7.5 SP4 (7.5.303). Namely, it wouldn't run. It's not compatible with Server 2008 R2 unless you're running SP5. Well, we're going to get rid of it in a few months but we want to get rid of our 4.5 farm. So, we need to migrate the application to XenApp 6 and Server 2008R2 from Presentation Server 4.5 and Server 2003 SP1.

First thing I did was setup a Server 2003 SP1 box and installed the AppV sequencer on it and sequenced the application. I then set it to run on 2008R2 64bit and moved the package over to it. It would crash. Analysing the crash logs would present to me the error... ERAPP32 was crashing its heap. In order to get it to work I had to set it to run in compatibility mode for XPSP3. Once I set this it worked flawlessly. So what I needed to do was push this fix to the rest of our Citrix servers before deploying the AppV application. If you've ever read ACT (application compatibilty toolkit) and merging it with AppV it's kind of a difficult job.

But there is a easier way.

Stored in the registry is the AppCompatFlags key that contains the applications and the shims you can apply to an application. If you put the path to your AppV application it will actually enable it to run in the compatibility mode that you specify. This was my registry entry:

Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Layers]
"Q:\\Enterprise Reporting\\ErApp\\erapp32.exe"="WINXPSP3 256COLOR DISABLETHEMES DISABLEDWM HIGHDPIAWARE RUNASADMIN"

And now the application works almost wonderfully (ER is a painful application)

:)

Monday, March 05, 2012

Utilizing PowerShell to make Citrix VM Templates

Because my company doesn't utilize provisioining servers for deploy new Citrix XenApp servers, I've had to come up with a couple of PowerShell scripts to make VMWare Templates that I can then deploy multiple XenApp servers. You need VMWare PowerCLI to run this script. This is my script:

function create-template{


Param(
[CmdletBinding()]
[Parameter(ValueFromPipeline=$true,
Position=0,
Mandatory=$true,
HelpMessage="Insert Message")]
[ValidateNotNullOrEmpty()]
$InputObject,
[Parameter(Position=1,
Mandatory=$false,
HelpMessage="Insert Preferred Folder")]
$folder,
[Parameter(Position=2,
Mandatory=$false,
HelpMessage="Insert Preferred Target Datastore")]
$datastore,
[Parameter(Position=3,
Mandatory=$false,
HelpMessage="Insert Preferred Target Host")]
$vmhost,
[Parameter(Position=4,
Mandatory=$false,
HelpMessage="Insert Preferred Disk Storage Format")]
[ValidateSet("Thick","Thin")]
$format = "Thin"
)


PROCESS{
$InputObject | %{
$vm = Get-VM $_
$name = $vm.name
$newname = -join("clone-",$name)
$date = get-date -uformat "-%Y-%m-%d"
$templatename = -join("template-",$name,$date)
If ($datastore -eq $null){
$datastore = Get-Datastore -VM $vm
}
If ($folder -eq $null){
$folder = $vm.Folder
}
If ($vmhost -eq $null){
$vmhost = Get-Cluster -VM $vm | Get-VMHost | Get-Random | Where{$_ -ne $null}
}
Write-Host "VM = $vm"
Write-Host "Name = $name"
Write-Host "NewName = $newname"
Write-Host "DataStore = $datastore"
Write-Host "Folder = $folder"
Write-Host "VMHost = $vmhost"
write-Host "templatename=$templatename"
sleep 4

Write-Host "Setting up domain unjoin script..."
Remove-Item "\\$name\c$\ProgramData\Microsoft\Windows\Start Menu\Programs\Startup\remove.cmd"
Add-Content "\\$name\c$\ProgramData\Microsoft\Windows\Start Menu\Programs\Startup\remove.cmd" "netdom remove $name /Force"
Add-Content "\\$name\c$\ProgramData\Microsoft\Windows\Start Menu\Programs\Startup\remove.cmd" "netdom renamecomputer $name /newname:XA6TEMPLATE /Force"
Add-Content "\\$name\c$\ProgramData\Microsoft\Windows\Start Menu\Programs\Startup\remove.cmd" "REG ADD `"HKEY_LOCAL_MACHINE\SYSTEM\Setup\Status\SysprepStatus`" /v `"GeneralizationState`" /t

REG_DWORD /d 0x7 /f"
Add-Content "\\$name\c$\ProgramData\Microsoft\Windows\Start Menu\Programs\Startup\remove.cmd" "REG ADD `"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion

\SoftwareProtectionPlatform`" /v `"SkipRearm`" /t REG_DWORD /d 0x1 /f"
Add-Content "\\$name\c$\ProgramData\Microsoft\Windows\Start Menu\Programs\Startup\remove.cmd" "`"C:\Program Files (x86)\Citrix\XenApp\ServerConfig\XenAppConfigConsole.exe`"

/ExecutionMode:ImagePrep /PrepMsmq:True"
Add-Content "\\$name\c$\ProgramData\Microsoft\Windows\Start Menu\Programs\Startup\remove.cmd" "shutdown -s -t 90 -f"
Add-Content "\\$name\c$\ProgramData\Microsoft\Windows\Start Menu\Programs\Startup\remove.cmd" "del /q `"c:\ProgramData\Microsoft\Windows\Start Menu\Programs\Startup\remove.cmd"
Write-Host "Setting Autologon..."
psexec \\$name "REG.EXE" ADD `"\\$name\HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon`" /v `"DefaultUserName`" /d Administrator /f`"
sleep 5
psexec \\$name "REG.EXE" ADD `"\\$name\HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon`" /v `"DefaultPassword`" /d Hello /f`"
sleep 5
psexec \\$name "REG.EXE" ADD `"\\$name\HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon`" /v `"AutoAdminLogon`" /t REG_DWORD /d 0x1 /F`"
sleep 5
psexec \\$name "REG.EXE" ADD `"\\$name\HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon`" /v `"AutoLogonCount`" /t REG_DWORD /d 0x1 /F`"

Write-Host "Cloning"
New-VM -Name $newname -VM $vm -Location $folder -Datastore $datastore -VMHost $vmhost -DiskStorageFormat $format
Write-Host "Unplugging NIC..."
get-VM $newname | get-networkadapter | set-networkadapter -startconnected:$false -confirm:$false
Write-Host "Starting VM..."
start-vm $newname

Write-Host "Powering on clone..."
Remove-Item "\\$name\c$\ProgramData\Microsoft\Windows\Start Menu\Programs\Startup\remove.cmd"

Write-Host "We need to wait until the clone is powered off before we can template it..."

do {
sleep 1.0
Write "Waiting for shutdown of VMs"
} until (Get-VM $newname | Where { $_.PowerState -eq "poweredOff" })
write-host "System is powered on"

Write-Host "Creating NIC..."
get-VM $newname | get-networkadapter | set-networkadapter -startconnected:$true -confirm:$false
Write-Host "Removing any stale template-VM's"
remove-template $templatename -confirm:$false
Write-Host "Creating Template VM from clone"
new-template -VM $newname -name $templatename -Location $folder

Write-Host "Removing Clone VM"
remove-vm $newname -DeletePermanently -confirm:$false
}
}
}

This script does the following:
1) Sets the inputs from a piped in object (get-vm VMTOTEMPLATE | create-template)
2) Sets a series of variables ($vm, $name, $newname, $date, $templatename, etc.)
3) We setup a startup script on the target server to make into a template that:
a) Removes the computer from the domain
b) renames the computer to a generic name (XATEMPLATE)
c) Adds registry keys that will allow sysprep to run
d) Configures XenApp to "Image" mode
e) Shuts itself down once running the script is complete
f) deletes the script from running on startup
4) We then set the target to autologin with the local admin user name and password so the startup script in step 3 will be run
5) Begins the cloning by making a new-vm with the target machine
6) We unplug the NIC from VMWare so that when it starts up the script won't actually remove the machine from the domain, but will remove itself from the domain
7) start the clone
8) the PowerCLI will now wait till the machine turns itself off...
9) Then it will reconnect the NIC, remove any stale templates and then makes a new template and then removes the clone VM.

Done! :)

Utilizing PowerShell to make Citrix VM Templates

Because my company doesn't utilize provisioining servers for deploy new Citrix XenApp servers, I've had to come up with a couple of PowerShell scripts to make VMWare Templates that I can then deploy multiple XenApp servers. You need VMWare PowerCLI to run this script. This is my script:

function create-template{


Param(
[CmdletBinding()]
[Parameter(ValueFromPipeline=$true,
Position=0,
Mandatory=$true,
HelpMessage="Insert Message")]
[ValidateNotNullOrEmpty()]
$InputObject,
[Parameter(Position=1,
Mandatory=$false,
HelpMessage="Insert Preferred Folder")]
$folder,
[Parameter(Position=2,
Mandatory=$false,
HelpMessage="Insert Preferred Target Datastore")]
$datastore,
[Parameter(Position=3,
Mandatory=$false,
HelpMessage="Insert Preferred Target Host")]
$vmhost,
[Parameter(Position=4,
Mandatory=$false,
HelpMessage="Insert Preferred Disk Storage Format")]
[ValidateSet("Thick","Thin")]
$format = "Thin"
)


PROCESS{
$InputObject | %{
$vm = Get-VM $_
$name = $vm.name
$newname = -join("clone-",$name)
$date = get-date -uformat "-%Y-%m-%d"
$templatename = -join("template-",$name,$date)
If ($datastore -eq $null){
$datastore = Get-Datastore -VM $vm
}
If ($folder -eq $null){
$folder = $vm.Folder
}
If ($vmhost -eq $null){
$vmhost = Get-Cluster -VM $vm | Get-VMHost | Get-Random | Where{$_ -ne $null}
}
Write-Host "VM = $vm"
Write-Host "Name = $name"
Write-Host "NewName = $newname"
Write-Host "DataStore = $datastore"
Write-Host "Folder = $folder"
Write-Host "VMHost = $vmhost"
write-Host "templatename=$templatename"
sleep 4

Write-Host "Setting up domain unjoin script..."
Remove-Item "\\$name\c$\ProgramData\Microsoft\Windows\Start Menu\Programs\Startup\remove.cmd"
Add-Content "\\$name\c$\ProgramData\Microsoft\Windows\Start Menu\Programs\Startup\remove.cmd" "netdom remove $name /Force"
Add-Content "\\$name\c$\ProgramData\Microsoft\Windows\Start Menu\Programs\Startup\remove.cmd" "netdom renamecomputer $name /newname:XA6TEMPLATE /Force"
Add-Content "\\$name\c$\ProgramData\Microsoft\Windows\Start Menu\Programs\Startup\remove.cmd" "REG ADD `"HKEY_LOCAL_MACHINE\SYSTEM\Setup\Status\SysprepStatus`" /v `"GeneralizationState`" /t

REG_DWORD /d 0x7 /f"
Add-Content "\\$name\c$\ProgramData\Microsoft\Windows\Start Menu\Programs\Startup\remove.cmd" "REG ADD `"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion

\SoftwareProtectionPlatform`" /v `"SkipRearm`" /t REG_DWORD /d 0x1 /f"
Add-Content "\\$name\c$\ProgramData\Microsoft\Windows\Start Menu\Programs\Startup\remove.cmd" "`"C:\Program Files (x86)\Citrix\XenApp\ServerConfig\XenAppConfigConsole.exe`"

/ExecutionMode:ImagePrep /PrepMsmq:True"
Add-Content "\\$name\c$\ProgramData\Microsoft\Windows\Start Menu\Programs\Startup\remove.cmd" "shutdown -s -t 90 -f"
Add-Content "\\$name\c$\ProgramData\Microsoft\Windows\Start Menu\Programs\Startup\remove.cmd" "del /q `"c:\ProgramData\Microsoft\Windows\Start Menu\Programs\Startup\remove.cmd"
Write-Host "Setting Autologon..."
psexec \\$name "REG.EXE" ADD `"\\$name\HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon`" /v `"DefaultUserName`" /d Administrator /f`"
sleep 5
psexec \\$name "REG.EXE" ADD `"\\$name\HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon`" /v `"DefaultPassword`" /d Hello /f`"
sleep 5
psexec \\$name "REG.EXE" ADD `"\\$name\HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon`" /v `"AutoAdminLogon`" /t REG_DWORD /d 0x1 /F`"
sleep 5
psexec \\$name "REG.EXE" ADD `"\\$name\HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon`" /v `"AutoLogonCount`" /t REG_DWORD /d 0x1 /F`"

Write-Host "Cloning"
New-VM -Name $newname -VM $vm -Location $folder -Datastore $datastore -VMHost $vmhost -DiskStorageFormat $format
Write-Host "Unplugging NIC..."
get-VM $newname | get-networkadapter | set-networkadapter -startconnected:$false -confirm:$false
Write-Host "Starting VM..."
start-vm $newname

Write-Host "Powering on clone..."
Remove-Item "\\$name\c$\ProgramData\Microsoft\Windows\Start Menu\Programs\Startup\remove.cmd"

Write-Host "We need to wait until the clone is powered off before we can template it..."

do {
sleep 1.0
Write "Waiting for shutdown of VMs"
} until (Get-VM $newname | Where { $_.PowerState -eq "poweredOff" })
write-host "System is powered on"

Write-Host "Creating NIC..."
get-VM $newname | get-networkadapter | set-networkadapter -startconnected:$true -confirm:$false
Write-Host "Removing any stale template-VM's"
remove-template $templatename -confirm:$false
Write-Host "Creating Template VM from clone"
new-template -VM $newname -name $templatename -Location $folder

Write-Host "Removing Clone VM"
remove-vm $newname -DeletePermanently -confirm:$false
}
}
}

This script does the following:
1) Sets the inputs from a piped in object (get-vm VMTOTEMPLATE | create-template)
2) Sets a series of variables ($vm, $name, $newname, $date, $templatename, etc.)
3) We setup a startup script on the target server to make into a template that:
a) Removes the computer from the domain
b) renames the computer to a generic name (XATEMPLATE)
c) Adds registry keys that will allow sysprep to run
d) Configures XenApp to "Image" mode
e) Shuts itself down once running the script is complete
f) deletes the script from running on startup
4) We then set the target to autologin with the local admin user name and password so the startup script in step 3 will be run
5) Begins the cloning by making a new-vm with the target machine
6) We unplug the NIC from VMWare so that when it starts up the script won't actually remove the machine from the domain, but will remove itself from the domain
7) start the clone
8) the PowerCLI will now wait till the machine turns itself off...
9) Then it will reconnect the NIC, remove any stale templates and then makes a new template and then removes the clone VM.

Done! :)

Citrix IMA service issues

If Citrix is giving you grief because IMA won't start after unjoining and rejoining a farm, do the following:
IMA Service Fails to Start and MFCOM Service Hangs in a Starting State
Document ID: CTX127922 / Created On: 20-Jan-2011 / Updated On: 20-Oct-2011
Average Rating: (5 ratings)
View products this document applies to

Symptoms
IMA Service fails to start and MFCOM Service hangs in a Starting state.
Event ID: 7024
The Independent Management Architecture service terminated with service-specific error: 2147483649 (0x80000001).
- Or –
The IMA service terminated with service-specific error: 2147483647
Cause
When looking into the services manager, the MFCOM Service is in status “starting”. MFCOM and IMA Service fail to start because of a corrupt radeoffline DB.
Note: This issue also occurs after an incomplete or corrupted install of a Citrix Hotfix. Make sure you terminate the MFCOM32.exe as instructed below and re-install the hotfix properly. This can also ensure that the MFCOM Service will start successfully.
Resolution
Stop the mfcom.exe service using Task Manager.
Execute the following commands:
Dsmaint recreatelhc
Dsmaint recreaterade
Start the IMA Service and MFCOM Service.

Wednesday, February 29, 2012

Issue with Folder Redirection (Error %%267)

So we were having an issue with Folder Redirection today with a user. We were moving them from a fileshare to a UNC path. The difference is a file share is explicitly shared, the userdirs$ is a share that then has a folder within.

This was the error in fdeploy.log (enabled here).

16:32:10:116 Homedir redirection path %HOMESHARE%%HOMEPATH% expanded to \\newserver\userdirs$\user1.
16:32:10:116 Redirecting folder My Documents to \\%HOMESHARE%%HOMEPATH%.
16:32:10:116 Previous folder path \\oldserver\user1$ expanded to \\oldserver\user1$.
16:32:10:116 New folder path \\%HOMESHARE%%HOMEPATH% expanded to \\newserver\userdirs$\user1.
16:32:11:007 Failed to perform redirection of folder My Documents.
The folder is configured to be redirected from <\\oldserver\user1$> to <\\newserver\userdirs$\user1>.
The following error occurred:
%%267

%%267. After some brief investigation it was found this users had a huge My Docs and it was copied to the new server. The old directory was then renamed (user1.old) and a new directory was created called user1 and reshared as user1$ on the old server. Then this issue occurred. The reason this issue occurred was because the user wasn't assigned any permissions on the old directory so it was erroring out. I don't know if %%267 is a permission code or not but that's what we found. By adding the user to the folder \\oldserver\user1$ as full control did it then proceed and migrate his My Docs correctly.

Thursday, February 16, 2012

Registry keys needed to set a default server Exchange server for Outlook

We recently upgraded our Exchange infrastructure from 2007 to 2010. During this we changed the host name of the server from an internal name to a nice, easy to remember one (outlook.company.corp). But, we did not update our Outlook's defaults to this server, so when you open Outlook for the first time you are presented with this message:

Microsoft Exchange is unavailable.

With the options:
Retry, Work Offline, and Cancel. If you choose Work Offline you are given this error message:

"---------------------------
Microsoft Office Outlook
---------------------------
Outlook cannot log on. Verify you are connected to the network and are using the proper server and mailbox name. The connection to Microsoft Exchange is unavailable. Outlook must be online or connected to complete this action.
---------------------------
OK
---------------------------
"

then the opportunity to enter the server name to connect to:

If you correct the name here you will get an error where you can close Outlook then reopen it for it to operate. Another option is to change the default of that server to the correct one. I figured out the registry keys to do those so you can place in a GPO object.

Here are the keys:

Windows Registry Editor Version 5.00

[HKEY_CURRENT_USER\Software\Microsoft\Windows NT\CurrentVersion\Windows Messaging Subsystem\Profiles\ttye\8fa3465791d2b746a2c7d11ca063b282]
"001e660c"="outlook.company.corp"

[HKEY_CURRENT_USER\Software\Microsoft\Windows NT\CurrentVersion\Windows Messaging Subsystem\Profiles\ttye\406169da1432914e8075fa26cbcdc53a]
"001e660c"="outlook.company.corp"

[HKEY_CURRENT_USER\Software\Microsoft\Windows NT\CurrentVersion\Windows Messaging Subsystem\Profiles\ttye\1e8559cf990bb44792093b0cdfca0186]
"001e660c"="outlook.company.corp"

[HKEY_CURRENT_USER\Software\Microsoft\Windows NT\CurrentVersion\Windows Messaging Subsystem\Profiles\ttye\13dbb0c8aa05101a9bb000aa002fc45a]
"001e6602"="outlook.company.corp"

[HKEY_CURRENT_USER\Software\Microsoft\Windows NT\CurrentVersion\Windows Messaging Subsystem\Profiles\ttye\dca740c8c042101ab4b908002b2fe182]
"001f662a"=hex:6f,00,75,00,74,00,6c,00,6f,00,6f,00,6b,00,2e,00,63,00,63,00,73,\
00,2e,00,63,00,6f,00,72,00,70,00,00,00


The last key is a REG_BINARY of the server name (outlook.company.corp). If we make this into a GPO object then these keys can be placed in and users can connect to Exchange without the messages above.

The GPO object needs to look like so:

Notice the %LogonUser% variable.

Each "Key Path" requires your username substituted for the %LogonUser% variable as well:

Set this GPO with a loopback processing setting and you're rolling. The negative that I've seen with this approach is that it will set the registry keys on a new login, but launching Outlook for the first time will overwrite them with the defaults set in the PRF. If you cancel out and relaunch the registry keys will apply again and the server you specified in them will work.

Or you can setup a DNS Alias, but this was an interesting exercise anyways :)

Wednesday, February 15, 2012

Purposeful limitations

I have a Citrix environment without a provisioning server. This means that since I'm going to build my VM's out as opposed to up I need to script a method to automate deployment of Citrix servers within the VM environment. Fortunately, VMWare gives us the PowerCLI. A PowerShell that you can use to manipulate VMWare.

While I was working on this I ran into some some weird issues while working with VMWare and deploying OS Customizations.

It turns out if you burn your 3 activations VMWare cannot customize the OS anymore from VMWare. BUT(!) there is a dirty little trick that works for Server 2008 R2 (only one I've tested anyhow) that can allow you to work around the issue. The issue is VMWare's OS Customizations does a Generalize pass using Sysprep. If you exceed the 3 activations, sysprep will fail because it will notice this at the Generalize pass. The solution that I've read is to add this line to the sysprep.xml file:



1



http://support.microsoft.com/kb/929828

Unfortunately, VMWare does not appear to have a way to allow you to add the SkipRearm to the XML that it generates through the OS Customization GUI. But you can add a couple of registry keys that appear to have the same effect. They are:

REG ADD "HKEY_LOCAL_MACHINE\SYSTEM\Setup\Status\SysprepStatus" /v "GeneralizationState" /t REG_DWORD /d 0x7 /f"
REG ADD "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\SoftwareProtectionPlatform" /v "SkipRearm" /t REG_DWORD /d 0x1 /f"


These two keys will signal to sysprep that this image can be generalized even though it has exceeded its activation count.

So, what I've found is you need to set these registry keys *prior* to shutting it down to convert into a template. Then when you deploy from template and it starts up and engages sysprep, sysprep will run without issue.

So how do you put this all together? Here is my scenario:
We do NOT have a golden image template of our Citrix environment. This is because it is incredibly fluid. Changes occur to applications on the servers fairly regularly and documentation/memory is difficult to ensure that when we commit to putting these changes into the template that they are actually done. So how are we going to do this? We are going to take a 3 prong approach:
1) We have a dev environment where developers can modify/change and generally mess up their VM's to their hearts content. The whole goal of this environment is to get their application working at 100%. The developers do not need to answer to anyone and have free reign to modify and experiment.
2) We have a test environment where, when the developers think the application is tweaked/modified/configured exactly right; will pass off documentation to me to install in this environment. Any errors or modifications that happen outside of their documentation will be further documented and verified.
3) Once step two is validated we can then push on to install on the production servers.

The issues we encounter is some of our application installs are huge, multi-step non-automated processes with large configuration tweaks post install. Once we get a solid install on one of the production servers our perferred method of redployment (because I've automated this process thus eliminating possible failure points) will be to template and redeploy with VMWare. This is the script I've written to accomplish this:


function Clone-List{

Param(
[CmdletBinding()]
[Parameter(ValueFromPipeline=$true,
Position=0,
Mandatory=$true,
HelpMessage="Insert Message")]
[ValidateNotNullOrEmpty()]
$InputObject,
[Parameter(Position=1,
Mandatory=$false,
HelpMessage="Insert Preferred Folder")]
$folder,
[Parameter(Position=2,
Mandatory=$false,
HelpMessage="Insert Preferred Target Datastore")]
$datastore,
[Parameter(Position=3,
Mandatory=$false,
HelpMessage="Insert Preferred Target Host")]
$vmhost,
[Parameter(Position=4,
Mandatory=$false,
HelpMessage="Insert Preferred Disk Storage Format")]
[ValidateSet("Thick","Thin")]
$format = "Thin"
)


PROCESS{
$InputObject | %{
$vm = Get-VM $_
$name = $vm.name
$newname = -join("clone-",$name)
$templatename = -join("template-",$name)
If ($datastore -eq $null){
$datastore = Get-Datastore -VM $vm
}
If ($folder -eq $null){
$folder = $vm.Folder
}
If ($vmhost -eq $null){
$vmhost = Get-Cluster -VM $vm | Get-VMHost | Get-Random | Where{$_ -ne $null}
}
Write-Host "VM = $vm"
Write-Host "Name = $name"
Write-Host "NewName = $newname"
Write-Host "DataStore = $datastore"
Write-Host "Folder = $folder"
Write-Host "VMHost = $vmhost"

Write-Host "Setting up domain unjoin script..."
Remove-Item "\\$name\c$\ProgramData\Microsoft\Windows\Start Menu\Programs\Startup\remove.cmd"
Add-Content "\\$name\c$\ProgramData\Microsoft\Windows\Start Menu\Programs\Startup\remove.cmd" "netdom remove $name /Force"
Add-Content "\\$name\c$\ProgramData\Microsoft\Windows\Start Menu\Programs\Startup\remove.cmd" "netdom renamecomputer $name /newname:XA6TEMPLATE /Force"
Add-Content "\\$name\c$\ProgramData\Microsoft\Windows\Start Menu\Programs\Startup\remove.cmd" "REG ADD `"HKEY_LOCAL_MACHINE\SYSTEM\Setup\Status\SysprepStatus`" /v `"GeneralizationState`" /t REG_DWORD /d 0x7 /f"
Add-Content "\\$name\c$\ProgramData\Microsoft\Windows\Start Menu\Programs\Startup\remove.cmd" "REG ADD `"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\SoftwareProtectionPlatform`" /v `"SkipRearm`" /t REG_DWORD /d 0x1 /f"
Add-Content "\\$name\c$\ProgramData\Microsoft\Windows\Start Menu\Programs\Startup\remove.cmd" "shutdown -s -t 20 -f"
Add-Content "\\$name\c$\ProgramData\Microsoft\Windows\Start Menu\Programs\Startup\remove.cmd" "del /q `"c:\ProgramData\Microsoft\Windows\Start Menu\Programs\Startup\remove.cmd"
Write-Host "Setting Autologon..."
REG ADD "\\$name\HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" /v "DefaultUserName" /d Administrator /f
REG ADD "\\$name\HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" /v "DefaultPassword" /d 'password123' /f
REG ADD "\\$name\HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" /v "AutoAdminLogon" /t REG_DWORD /d 0x5 /F
REG ADD "\\$name\HKLM\Software\Microsoft\Windows NT\CurrentVersion\Winlogon" /v "AutoLogonCount" /t REG_DWORD /d 0x1 /F

Write-Host "Preparing XenApp for Cloning..."
psexec \\$name "C:\Program Files (x86)\Citrix\XenApp\ServerConfig\XenAppConfigConsole.exe" /ExecutionMode:ImagePrep /PrepMsmq:True

Write-Host "Cloning"
New-VM -Name $newname -VM $vm -Location $folder -Datastore $datastore -VMHost $vmhost -DiskStorageFormat $format
Write-Host "Unplugging NIC..."
get-VM $newname | get-networkadapter | set-networkadapter -startconnected:$false -confirm:$false
Write-Host "Starting VM..."
start-vm $newname

Write-Host "Powering on clone..."

REG ADD "\\$name\HKLM\SYSTEM\Setup\Status\SysprepStatus" /v "GeneralizationState" /t REG_DWORD /d 0x3 /f
Remove-Item "\\$name\c$\ProgramData\Microsoft\Windows\Start Menu\Programs\Startup\remove.cmd"
REG DELETE "\\$name\HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" /v "DefaultUserName" /f
REG DELETE "\\$name\HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" /v "DefaultPassword" /f
REG DELETE "\\$name\HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" /v "AutoAdminLogon" /f
REG DELETE "\\$name\HKLM\Software\Microsoft\Windows NT\CurrentVersion\Winlogon" /v "AutoLogonCount" /f

Write-Host "We need to wait until the clone is powered off before we can template it..."

do {
sleep 1.0
Write "Waiting for shutdown of VMs"
} until (Get-VM $newname | Where { $_.PowerState -eq "poweredOff" })
write-host "System is powered on"

Write-Host "Creating NIC..."
get-VM $newname | get-networkadapter | set-networkadapter -startconnected:$true -confirm:$false
Write-Host "Removing any stale template-VM's"
remove-template $templatename -confirm:$false
Write-Host "Creating Template VM from clone"
new-template -VM $newname -name $templatename -Location $folder
Write-Host "Removing Clone VM"
remove-vm $newname -DeletePermanently -confirm:$false


Write-Host "Setting up OS customization"
$cs = Get-OSCustomizationSpec PowerCLI
Set-OSCustomizationSpec -OSCustomizationSpec PowerCLI -ChangeSID:$true -Domain CCS.CORP -DomainUsername admin-ttye -DomainPassword password123 -NamingScheme Fixed -NamingPrefix CDCVXAP03P
new-vm -VMHost (Get-Cluster -VM $vm | Get-VMHost | Get-Random | Where{$_ -ne $null}) -name CDCVXAP03P -Template template-CDCVXAP02P -Datastore $datastore -OSCustomizationSpec $cs
get-VM CDCVXAP03P | get-networkadapter | set-networkadapter -startconnected:$true -confirm:$false

}
}
}


I've shamelessly stolen and modified this script from elsewhere. To execute this script, copy and paste it in a PowerGUI prompt then run:


get-vm CDCVXAP02P | Clone-List


It will execute the following:
1) Pull the following parameters from the target VM to reclone:
The VM Object
The VM Objects Name
The New Name for the temp clone
A Template Name
The Datastore the VM resides on
The folder the VM resides in
And the VM Host
2) We then copy a script to our target machine that does the following:
Removes the machine from the domain
Renames the machine
Prepares the addition of our two Sysprep registry key fixes
Prepares a 20 second count down
Deletes the script that executes step 2.
3) We add the default username and password and autologon registry values. As of this writting this is not working for some reason
4) We execute the command that preps the machine for cloning with Citrix. The machine you run this against will need to be rebooted as this will prevent new logons, but existing should continue (I think).
5) We start the cloning process and unplug the NIC on the new machine. We don't want the new machine to come up and unjoin the original machine from the domain. From here, it will auto-power on. Ideally, it will login automatically and run the preconfigured script. (You may have to login manually to get it to do its thing) Once done it should shut itself down.
6) Now VMWare will wait until the machine is powered off. Once it's powered off it will reconnect the NIC and make a template out of the VM and delete the temporary clone.
7) Lastly it will now setup the OS Customization dynmically and create a VM with it.

Voila!

Tuesday, January 31, 2012

PowerShell to Clone VM's

I need to create a Provisioning Server of my own. We don't want to purchase the software to do so and we may not need to do so... VMWare has PowerCLI which may provide enough to do the following:

1) Notify the VM that it needs to disable Citrix logins
2) Have the VM disable logins and then check for logins. If none are found kick-off the cloning process. Kick off consists of:
2a) Unjoin from the domain
2b) Shutdown
If users are still logged in, log them off forcefully at midnight and start the process
3) Use VMWare Templates to clone the VM with the VM name.
4) Shutdown original VM's with the same names
5) Startup new VM's and join to domain...

Done?

I have a script that does the cloning... I got it from this site:
http://www.vtesseract.com/post/16447807254/clone-list-powercli-function
http://communities.vmware.com/docs/DOC-18155

I had issues with running it though. For some reason my PowerShell wouldn't run it with the comments in it so I had to take them out:


function Clone-List{


Param(
[CmdletBinding()]
[Parameter(ValueFromPipeline=$true,
Position=0,
Mandatory=$true,
HelpMessage="Insert Message")]
[ValidateNotNullOrEmpty()]
$InputObject,
[Parameter(Position=1,
Mandatory=$false,
HelpMessage="Insert Preferred Folder")]
$folder,
[Parameter(Position=2,
Mandatory=$false,
HelpMessage="Insert Preferred Target Datastore")]
$datastore,
[Parameter(Position=3,
Mandatory=$false,
HelpMessage="Insert Preferred Target Host")]
$vmhost,
[Parameter(Position=4,
Mandatory=$false,
HelpMessage="Insert Preferred Disk Storage Format")]
[ValidateSet("Thick","Thin")]
$format = "Thin"
)


PROCESS{
$InputObject | %{
$vm = Get-VM $_
$name = $vm.name
$newname = -join("clone-",$name)
If ($datastore -eq $null){
$datastore = Get-Datastore -VM $vm
}
If ($folder -eq $null){
$folder = $vm.Folder
}
If ($vmhost -eq $null){
$vmhost = Get-Cluster -VM $vm | Get-VMHost | Get-Random | Where{$_ -ne $null}
}
New-VM -Name $newname -VM $vm -Location $folder -Datastore $datastore -VMHost $vmhost -DiskStorageFormat $format -RunAsync
}
}
}


You can run it with a command like so:
Get-VM MyVM | Clone-List


I will need to modify this script to see if I can use VMWare Templates (I think that's the right terminology) and Citrix XA PowerShell to see if I can get this to work... We shall see :)

EDIT - It's not VMWare Templates... It's OSCustomizationSpec I think.

Wednesday, January 11, 2012

Cool PowerShell commands for manipulating XenApp

After installing the XenApp 6 SDK you can do some neat PowerShell scripts to help move things around. I recently created a test farm and needed a way to move all the applications and their settings from the original farm. These commands did it:

First: Export the XenApp configuration

http://www.jasonconger.com/post/migrate-citrix-xenapp-6-folder-structure-using-powershell/
http://www.jasonconger.com/post/export-and-import-citrix-xenapp-6-published-applications-using-powershell/

Get-XAApplicationReport * | Export-Clixml c:\testingApps.xml

Copy C:\testingapps.xml to the new server.
Create the folder structure:

Import-Clixml c:\testingapps.xml | New-XAFolder

Load your previous application settings:
Import-Clixml c:\testingApps.xml | New-XAApplication -WorkerGroupNames $null

Friday, November 18, 2011

Group Creation Script

The final script:
:This script will create the proper domain local group and global groups
:The only input this script needs is the UNC path to the folder. At the end
:of this script the only thing you'll need to do is add the users from the actual
:folder to the global group.
:
:eg create-groups.cmd "\\file01\HR\HR Support Group\HR Team\HR Support Services"

SET /P Permissions=What permissions will this group have ([F]ULL/[M]ODIFIY/[RO]READ ONLY)?

IF /I '%PERMISSIONS%' EQU 'F' SET PERMISSIONS=FULL
IF /I '%PERMISSIONS%' EQU 'M' SET PERMISSIONS=MODIFY
IF /I '%PERMISSIONS%' EQU 'RO' SET PERMISSIONS=RO





:ECHO %1 |sed "s/\\\\//g"|sed "s/\\/\./g" | sed "s/\"//g"

:We parse the command line for the UNC structure, now we need to find the last folder
for /F "tokens=*" %%A IN ('ECHO %1 ^|sed ^"s/\\\\//g^"^|sed ^"s/\\/\./g^" ^| sed ^"s/\^"//g^"') DO set groupname=%%A


set str=%groupname%
set N=0
setLocal EnableDELAYedExpansion

:loop
if !N! equ 55 (
goto :exceedcharacterquota
)

set /A N+=1

ECHO N=!N!
if "!str:~1!" neq "" (
set str=!str:~1!
goto :loop
)
goto :skip-string-modification

:if string length exceeds 55 chars, take the first 25 chars and the last 25 chars with an ellipse (...)
:in between.
:exceedcharacterquota
set string-part-one=!groupname:~0,25!
set string-part-two=!groupname:~-25!
set GROUPNAME=!string-part-one!...!string-part-two!


:skip-string-modification
setLocal disableDELAYedExpansion
:Remove any trailing spaces
for /F "tokens=*" %%A IN ('ECHO %GROUPNAME% ^|sed ^"s/ $//g^"') DO set groupname=%%A
ECHO GROUP=%GROUPNAME%

:Sets OU to domain local resource group...
SET OUL=OU=Resource,OU=Security Groups,OU=AD Project 3,DC=CCS,DC=CORP
dsadd group "CN=F.lg.%GROUPNAME%.%PERMISSIONS%,%OUL%" -desc %1 -secgrp yes -scope l

:Sets OU to Global group...
SET OUG=OU=Global,OU=Security Groups,OU=AD Project 3,DC=CCS,DC=CORP
dsadd group "CN=gg.%GROUPNAME%.%PERMISSIONS%,%OUG%" -desc %1 -secgrp yes -scope g

:adds the global group to the domain local group
dsmod group "CN=F.lg.%GROUPNAME%.%PERMISSIONS%,%OUL%" -addmbr "CN=gg.%GROUPNAME%.%PERMISSIONS%,%OUG%"

Count Characters in a Batch File

I have an issue where I need to ensure I don't exceed a certain number of characters in a script. Specifically, I cannot exceed 64 characters while making a group through script in AD. To do this I came up with the following:

ECHO OFF
:Set some variables, the string variable, a second string variable and our starting count.
set str=\\fileservertestserver\accounting\Budgets\Budgets 2012\2012 Budget GA Loads
set str2=%str%
set N=0
setLocal EnableDELAYedExpansion

:the loop works like so... we check to see if N has been incremented to 55 (our
:target number of characters). If it has, it goes to a new routine which creates
:our path. If it does not, we increment N by one then check to see if the string
:has reached a NULL character. If it has not reached "55" then goto loop and repeat.
:loop
if !N! equ 55 (
goto :exceedcharacterquota
)

set /A N+=1

ECHO N=!N!
if "!str:~1!" neq "" (
set str=!str:~1!
goto :loop
)

:if string length exceeds 55 chars, take the first 25 chars and the last 25 chars with an ellipse (...)
:in between.
:exceedcharacterquota
set string-part-one=!str2:~0,25!
set string-part-two=!str2:~-25!
echo !string-part-one!...!string-part-two!

Monday, November 07, 2011

Working around a VPN administrator issue...

We recently had a workstation that was sent to a user that didn't have the user in the local administrator group. This should be an easy fix, just add the user to that group. We could not though, as the user was a domain user and the computer was not on a network with a domain controller. I did have the local administrator account password and could login with it, but the VPN technology we use (Juniper's Network Connect) requires local administrator rights to install it (which I was able to do) but is setup to verify that the user account is a domain user account. Without the user account being from the domain and a local administrator account, Network Connect will not connect to our network.

Well, this is a bit of a pickle. Since this user was in Victoria BC and I was in Calgary AB, I needed to find a way to connect their computer to our domain network so they could authenticate against our DC and then I could add their account to the local admin group.

To do this I did the following:

1) Using the local administrator account on the remote computer, install TeamViewer and set it to launch on Windows Startup.
2) Setup unattended access through TeamViewer and install the VPN client on the remote computer and my local computer.
3) The network the remote computer was on was a 192.168.X.X and our network is a 10.X.X.X. So I set a persistent route on the remote computer to route all traffic to the 10/8 network to the VPN ip on *my* (the local) end "route add 10.0.0.0 mask 255.0.0.0 7.154.200.11 -p"
4) Add a DNS entry on both the Teamviewer and ethernet adapter of the remote computer to the DNS on one of the computers in the Domain (a 10.X.X.X address).
5) I downloaded and installed NAT32 onto the local computer. I configured it like so:
Teamviewer VPN Adapter - Private
Local Network Adapter - Internet
"Share the Windows IP Address"

Then NAT32 generated a screen like so:


From here, I connected to the user through Teamviewer's VPN and via Remote Control. I confirmed I could ping the DNS server on the domain from the remote computer. I rebooted the computer, phoned the user and found out when it was at the CTRL-ALT-DEL screen. Once it was at that stage, I connected to it, from TeamViewer's VPN, and then I could login to the domain. While under the users account, I started an elevated command-prompt and opened compmgmt.msc and added her domain account to the local admin account. I then had her log off, and via Teamviewer's remote control, logged back on via the local admin account. I then removed the persistent static route and logged back off and had the user log back in. From here, she had all the rights she needed to launch Network Connect and Network Connect saw that this computer is connected to the domain and allowed connection.

http://kb.juniper.net/InfoCenter/index?page=content&id=KB9084

Tuesday, October 25, 2011

Cool tool!

Mariano Sergio Cosentino created a script that will convert registry keys into ADMX template files. This is awesome as the alternative to deploying large number of registry keys and values is typically a startup script with regedit.exe /s %regfile%.

http://mscosentino-en.blogspot.com/2010/02/convert-registry-file-to-admx-policy.html

Tool is available here:
http://www.mscosentino.com/desarrollos/reg2admxl/reg_2_admx.vbs

Usage is: CSCRIPT REG_2_ADMXL.vbs registry-file language [name]

I used this tool to create a ADMX template of the following registry key:
KEY_CURRENT_USER\Software\Microsoft\Windows NT\CurrentVersion\Windows Messaging Subsystem

We use Microsoft fRX and it utilizes this key to determine your mail prefences if you're using exchange. If you have the old Office 2000/2003 (IIRC) you should have this key. 2007 and greater now use a different method of storing email account information (apparently). This content is generated by using the "Mail" control panel icon. We used this tool to prestage the server name and a "Windows Messaging Profile" so that when you try to email from fRX you don't go through a complicated wizard asking for things like "server name". If you're organization is like ours, your internal email server name is something users won't know and won't be able to guess (eg, 3-digit-company-abbr,3-digit-code-for-prod-or-dev,3-digit-code-for-virtual-or-physical,3-digit-code-for-server-role(eg EXC-exchange),3-digit-code-for-number).

Friday, October 14, 2011

LDAP query for *just* users

We have numerous "mailbox only" user accounts in our AD. I've been asked for a query of all the user accounts on our domain. The query needs to exclude these accounts and disabled accounts as we're only interested in active user accounts. This is what I came up with:

adfind -f "&(objectcategory=person)(samaccountname=*)(!(userAccountControl:1.2.840.113556.1.4.803:=2)(!(msExchRecipientTypeDetails=4)(!(msExchRecipientDisplayType=7)(!(msExchRecipientDisplayType=8)(!(extensionattribute1=Service Account))))))" -csv -csvdelim ;

This query does the following:
Find all user accounts (objectcategory=person)(samaccountname=*)
But NOT
Disabled accounts (userAccountControl:1.2.840.113556.1.4.803:=2)
Exchange Shared Mailboxes: (msExchRecipientTypeDetails=4)
Exchange Rooms: (msExchRecipientDisplayType=7)
Exchange Equipment: (msExchRecipientDisplayType=8)
Service Accounts: (extensionattribute1=Service Account)

MS Software usually adds "SERVICE ACCOUNT" to the extensionattribute1.

Thursday, September 15, 2011

Change file shares via scripting

I've come across a problem where users are filling up their hard disks and we need to move the highest utilization users to a new disk. In order to accomplish this I've setup a robocopy to move their files to a new disk and have it constantly mirrored until after-hours; where we run this script to move the file shares:


:backup original shares:
reg export "HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Services\lanmanserver\Shares" C:\shares-backup.reg /y

setlocal enabledelayedexpansion
:what we need to do is grab the user name and the key...
for /f "tokens=1-2*" %%A IN ('reg query "HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Services\lanmanserver\Shares" ^| findstr /I /C:"E:\User Files\Corporate" ^| sed.exe "s/Path=E:\\User Files\\Corporate/Path=G:\\User Files\\Corporate/"') DO (
echo reg add HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Services\lanmanserver\Shares /v %%A /t %%B /D "%%C" /f
)

for /f "tokens=1-2*" %%A IN ('reg query "HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Services\lanmanserver\Shares" ^| findstr /I /C:"E:\User Files\Finance" ^| sed.exe "s/Path=E:\\User Files\\Finance/Path=G:\\User Files\\Finance/"') DO (
echo reg add HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Services\lanmanserver\Shares /v %%A /t %%B /D "%%C" /f
)

net stop server /y
net start server


What this script does is:
1) Backs up the existing share structure
2) Queries the file shares for the specific path of the share we're going to move
3) Using SED.exe we change the drive letter from E: to G:
4) Using reg.exe we overwrite the registry key with the new value
5) we then stop and restart the server service to get the new shares working.

And we set that up as a scheduled task to run after-hours :)