Tuesday, December 17, 2013

Slow PowerShell CSV reading and object generation

I am attempting to read a CSV file and decided the best way to do it was with PowerShell's native import-csv tool.  What was required was to read this CSV and then generate a registry file for import into numerous computers.  This was my result:

########################################################################################
#
#  Created by Trentent Tye
#             IBM Intel Server Team
#             December 13, 2013
#
# Description: This script will take a CSV file that contains values that are flexible
# and maintained by the MetaVision team and create a .reg file and import that file
# into the computer it's run on.
#########################################################################################

Get-Date
$csv = Import-csv .\MetaVision.csv

$output = "Windows Registry Editor Version 5.00`n`n"

$date = get-date
write-host first run $date
Foreach ($server in $csv.Server) { 
$regObject = $csv | Where {$_.server -eq $Server}
$output += "[HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\iMD Soft\Citrix Clients\$server\Database Connect]`n"
$output += """Default Offline Mode""=""0""`n"
$output += """Default  Offline Mode""=""0""`n"
$output += """Domain Department""=""{0}""`n" -f $regObject.'Domain Department'
$output += """Default Department""=""{0}""`n" -f $regObject.'Default Department'
$output += """EMPI Database""=""{0}""`n" -f $regObject.'EMPI Database'
$output += """EMPI Server""=""{0}""`n" -f $regObject.'EMPI Server'
$output += """Offline Mode""=""0""`n"
$output += """Production Database""=""{0}""`n" -f $regObject.'Production Database'
$output += """Production Server""=""{0}""`n`n" -f $regObject.'Production Server'
$output += "[HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\iMD Soft\Citrix Clients\$server\Settings]`n"
$output += """Application Size""=""1""`n"
$output += """Customize Units""=""0""`n"
$output += """DebugMode""=""0""`n"
$output += """DisplayLocalizationID""=""0""`n"
$output += """LastLogin""=""null""`n"
$output += """LocaleID""=""1033""`n"
$output += """LogFileMode""=""1""`n"
$output += """LogFilePath""=""""`n"
$output += """LoginPolicyMode""=""{0}""`n" -f $regObject.'LoginPolicyMode'
$output += """ProximityLockRange""=""0""`n"
$output += """ProximityObject""=""""`n"
$output += """ProximityUnlockRange""=""0""`n"
$output += """Silent""=""1""`n"
$output += """UseMRDDriver""=""0""`n"
$output += """UseVirtualKeyboard""=""0""`n"
$output += """Workstation BedID""=""{0}""`n" -f $regObject.'Workstation BedID'
$output += """Workstation LayoutID""=""""`n"
$output += """Workstation Type""=""{0}""`n" -f $regObject.'Workstation Type'
$output += """WriteActionsToLog""=""0""`n`n"
}

$date2 = get-date
$totaltime = ($date2 - $date).TotalSeconds / 60
write-host first complete $totaltime


rm "$env:temp\MetaReg.reg"
write-output $output | out-file -FilePath "$env:temp\MetaReg.reg"
Get-Date
write-host "Importing registry"
regedit /s "$env:temp\MetaReg.reg"
Get-Date

The CSV file we have has about 1500 lines in it.  To generate the registry key utilizing this method took 6 minutes and 45 seconds.  This is unacceptably slow.  I then started googling ways to speed up this processing and came across this article:
http://stackoverflow.com/questions/6386793/how-to-use-powershell-to-reorder-csv-columns

Where Roman Kuzmin suggested to handle the file as a text file instead of a PowerShell object.  The syntax used to generate convert the file into objects that can replace text as needed is a bit different but I decided to explore it.  His example code is as follows:


$reader = [System.IO.File]::OpenText('data1.csv')
$writer = New-Object System.IO.StreamWriter 'data2.csv'
for(;;) {
    $line = $reader.ReadLine()
    if ($null -eq $line) {
        break
    }
    $data = $line.Split(";")
    $writer.WriteLine('{0};{1};{2}', $data[0], $data[2], $data[1])
}
$reader.Close()
$writer.Close()
 
Essentially, he is proposing reading the file line by line and extracting the data by manually splitting the text string.  The string then turns into an array that you can use for substitution.  This is my final code using his example:

########################################################################################
#
#  Created by Trentent Tye
#             IBM Intel Server Team
#             December 13, 2013
#
# Description: This script will take a CSV file that contains values that are flexible
# and maintained by the MetaVision team and create a .reg file and import that file
# into the computer it's run on.
#########################################################################################


$date = get-date
write-host first run $date
if (test-Path $env:temp\MetaReg.reg) {rm "$env:temp\MetaReg.reg"}


$reader = [System.IO.File]::OpenText('MetaVision.csv')
$reader.ReadLine() # skip first line
$writer = New-Object System.IO.StreamWriter "$env:temp\MetaReg.reg"
$writer.WriteLine("Windows Registry Editor Version 5.00")
$writer.WriteLine("")

for(;;) {
    $line = $reader.ReadLine()
    if ($null -eq $line) {
        break
    }
    $data = $line.Split(",")
    $writer.WriteLine("[HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\iMD Soft\Citrix Clients\{0}\Database Connect]`n", $data[0])
    $writer.WriteLine("""Default Offline Mode""=""0""`n")
    $writer.WriteLine("""Default  Offline Mode""=""0""`n")
    $writer.WriteLine("""Domain Department""=""{0}""`n", $data[2])
    $writer.WriteLine("""Default Department""=""{0}""`n", $data[1])
    $writer.WriteLine("""EMPI Database""=""{0}""`n", $data[3])
    $writer.WriteLine("""EMPI Server""=""{0}""`n", $data[4])
    $writer.WriteLine("""Offline Mode""=""0""`n")
    $writer.WriteLine("""Production Database""=""{0}""`n", $data[5])
    $writer.WriteLine("""Production Server""=""{0}""`n`n", $data[6])
    $writer.WriteLine("")
    $writer.WriteLine("[HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\iMD Soft\Citrix Clients\{0}\Settings]`n", $data[0])
    $writer.WriteLine("""Application Size""=""1""`n")
    $writer.WriteLine("""Customize Units""=""0""`n")
    $writer.WriteLine("""DebugMode""=""0""`n")
    $writer.WriteLine("""DisplayLocalizationID""=""0""`n")
    $writer.WriteLine("""LastLogin""=""null""`n")
    $writer.WriteLine("""LocaleID""=""1033""`n")
    $writer.WriteLine("""LogFileMode""=""1""`n")
    $writer.WriteLine("""LogFilePath""=""""`n")
    $writer.WriteLine("""LoginPolicyMode""=""{0}""`n", $data[8])
    $writer.WriteLine("""ProximityLockRange""=""0""`n")
    $writer.WriteLine("""ProximityObject""=""""`n")
    $writer.WriteLine("""ProximityUnlockRange""=""0""`n")
    $writer.WriteLine("""Silent""=""1""`n")
    $writer.WriteLine("""UseMRDDriver""=""0""`n")
    $writer.WriteLine("""UseVirtualKeyboard""=""0""`n")
    $writer.WriteLine("""Workstation BedID""=""{0}""`n", $data[9])
    $writer.WriteLine("""Workstation LayoutID""=""""`n")
    $writer.WriteLine("""Workstation Type""=""{0}""`n", $data[10])
    $writer.WriteLine("""WriteActionsToLog""=""0""`n`n")
    $writer.WriteLine("")
}
$reader.Close()
$writer.Close()



$date2 = get-date
$totaltime = ($date2 - $date).TotalSeconds / 60
write-host first complete $totaltime



write-host "Importing registry"
regedit /s "$env:temp\MetaReg.reg"
Get-Date

The total time for this?  0.07 seconds.  Incredibly fast.  So, utilize the Import-CSV command and object based creation with caution.  Utilizing even a moderately sized file will take an unacceptable amount of time.

Wednesday, November 20, 2013

Microsoft and Citrix putting pictures of scripts in documents...

Come on guys, a little more effort than that.  How are you supposed to copy paste an image?

The script is the following:

It's supposed to pull the application and command-line to execute it in AppV5.  I got the script from this document:
http://www.microsoft.com/en-us/download/details.aspx?id=40885


"PackageName,Application Name,ApplicationPath"|out-file .\appPath.txt -Append
foreach($pkg in (gwmi -Namespace root\AppV -Class AppVClientPackage -Filter "IsPublishedGlobally = true"))
{

    $apps=gwmi -Namespace root\AppV -Class AppVClientApplication -Filter "PackageID = '$($pkg.PackageId)' and PackageVersionID = '$($pkg.VersionId)'"

   foreach($app in $apps)

   {

      $rootfolder=(Get-ItemProperty "HKLM:\Software\Microsoft\AppV\Client\Packages\$($app.PackageID)\Versions\$($app.PackageVersionID)\Catalog").Folder

      $appPath=$app.TargetPath.Replace("[{AppVPackageRoot}]",$rootFolder)

      "$($pkg.Name),$($app.Name),$($appPath)"|out-file .\AppPath.txt -Append
   }
}

Friday, November 15, 2013

Error launching batch file from within AppV 5 bubble

So we have an application that requires the "%CLIENTNAME%" variable to be passed to it in it's exe string.  The string looks like so:
prowin32.exe -p \\nas\cfgstart.p -param S,%CLIENTNAME%,120n,citrix,a92,10920 -wy

The issue we have is APPV does not seem to get that variable and pass it to the program.  So when the program starts, it makes %clientname% folders in a temp directory and we can't have two %clientname% folders in the same directory so only one instance of the application can be launched *period* if we do it this way, as opposed to one per server.

To resolve this issue I wrote a script that will pass the %CLIENTNAME% variable to AppV by ripping it out of the registry:

=======================================================
ECHO Launching Centricity...

for /f "tokens=1-3" %%A IN ('reg query "HKCU\volatile environment" /s ^| findstr /i /c:"CLIENTNAME"') DO SET CLIENTNAME=%%C

prowin32.exe -p \\nas\cfgstart.p -param S,%CLIENTNAME%,120n,citrix,a92,10920 -wy
=======================================================

This worked for AppV 4.6 without issue.  Now with AppV 5 I get an error, PATH NOT FOUND when trying to launch this script.


To verify the path exists in the app I ran the following commands:


The powershell commands put me in the AppV 5 bubble then opened a command prompt.  From the command prompt I can see the directory that is missing.  Going back to procmon I was curious to see what command it was launching.  It was launching this:

cmd /c ""C:\ProgramData\Microsoft\AppV\Client\Integration\3230251A-5B8E-47EF-8378-986B2A492D05\Root\VFS\Common Desktop\MyApps\BDM Pharmacy v9.2\Centricity Pharmacy Test on rxpv91cal.cmd" /appvve:3230251A-5B8E-47EF-8378-986B2A492D05_03CA3F94-F318-4693-A7E3-038DB30E6C70"

This command was failing.  It appears that when you are launching the .cmd file directly AppV 5 starts the cmd.exe *outside* the AppV bubble and it doesn't connect to the appvve.  To correct this I tried this command line:

cmd /c "cmd.exe /c "C:\ProgramData\Microsoft\AppV\Client\Integration\3230251A-5B8E-47EF-8378-986B2A492D05\Root\VFS\Common Desktop\MyApps\BDM Pharmacy v9.2\Centricity Pharmacy Test on rxpv91cal.cmd" /appvve:3230251A-5B8E-47EF-8378-986B2A492D05_03CA3F94-F318-4693-A7E3-038DB30E6C70"

Success!  It launched successfully and saw the directory and everything was good there after.  So let that be a lesson to other AppV 5 package makers, if you need a pre-launch script you may need to modify your published icon to put another cmd.exe /c before the command file for it to start in the bubble.

A very good AppV blog has already discovered this issue and came back with a better fix than mine:
http://blogs.technet.com/b/virtualvibes/archive/2013/10/17/the-issues-of-sequencing-bat-shortcuts-in-app-v-5-0.aspx

Wednesday, October 23, 2013

Citrix Provisioning Services (PVS) update failure

I recently got Event ID 0 on Citrix vDisk Update Service; "Update Task (UpdateTask) is not loaded"



The fix is to restart the Soap service on your chosen automatic update PVS server.





Tuesday, October 15, 2013

PowerCLI Fix VMWare Time Sync issue


# ===========================================================================================================
#
# Created by: Trentent Tye
#
# Creation Date: Oct 15, 2013
#
# File Name: Fix-Time-Sync.ps1
#
# Description: This script will be used to resolve an issue with VMWare where the VMWare Tools cause a
#                   time sync with the host.  If the host has an incorrect time it will knock the time out of
#                   sync on the guest.  To resolve this issue some text entires need to be made to the VMX
#                   fix.  This is detailed here:
#                   http://kb.vmware.com/selfservice/microsites/search.do?language=en_US&cmd=displayKC&externalId=1189
#
# ===========================================================================================================

Add-PSSnapin VMware.VimAutomation.Core
connect-viserver wsvcenter20

$ExtraOptions = @{
    "tools.syncTime"="0";
    "time.synchronize.continue"="0";
    "time.synchronize.restore"="0";
    "time.synchronize.resume.disk"="0";
    "time.synchronize.shrink"="0";
    "time.synchronize.tools.startup"="0";
    "time.synchronize.tools.enable" = "0";
    "time.synchronize.resume.host" = "0";
}   # build our configspec using the hashtable from above.  I prefer this
# method over the use of files b/c it has one less needless dependency.
$vmConfigSpec = New-Object VMware.Vim.VirtualMachineConfigSpec
# note we have to call the GetEnumerator before we can iterate through
Foreach ($Option in $ExtraOptions.GetEnumerator()) {
    $OptionValue = New-Object VMware.Vim.optionvalue
    $OptionValue.Key = $Option.Key
    $OptionValue.Value = $Option.Value
    $vmConfigSpec.extraconfig += $OptionValue
}
# Get all vm's starting with name:
$VMView = Get-View -ViewType VirtualMachine -Filter @{"Name" = "WSCTX"}

foreach($vm in $VMView){
    $vm.ReconfigVM_Task($vmConfigSpec)
}

# Get all vm's starting with name:
$VMView = Get-View -ViewType VirtualMachine -Filter @{"Name" = "WSAPV"}

foreach($vm in $VMView){
    $vm.ReconfigVM_Task($vmConfigSpec)
}