Tuesday, July 15, 2008

Assigning Citrix Load Evaluator from PowerShell

Well, still lot of stuff to learn about MFCOM behavior :(

I already run into this issue few times and I wanted to clarify that Citrix XenApp 4.5 have same issue.

There are two different ways how to assign Load Evaluator using MFCOM.

Server based

First makes more sense to me:

1.) Load server object

2.) Assign load evaluator to server

Problem with this approach is that sometimes (= quite often) something goes wrong in MFCOM. If you ask MFCOM about assigned load evaluator, you get correct answer:

PS C:\Temp> MFCOM:Assign-LoadEvaluator -Server ctxs-ctp -LoadEvaluator Default
True

In this case True means that load evaluator assigned is SAME as requested (-LoadEvaluator).

When I try to manually double-check, everything looks fine:

PS C:\Temp> $CtxServer = $CitrixFarm.GetServer([MF.Object]::WinSrvObject, "ctxs-ctp")
PS C:\Temp> $LE = $CtxServer.AttachedLE
PS C:\Temp> $LE.LoadData($True)
PS C:\Temp> $LE.LEName
MFDefaultLE
PS C:\Temp>

If you however look in CMC, you see old load evaluator:

image

Hmmmmm, that is really strange. To make it even more confusing, lets select Load Evaluators in CMC:

image Now change view to report "By Evaluator" - here we go, our server have 2 Load Evaluators assigned :(

image

 Load Evaluator based

You can also use second method

1.) Get Load Evaluator

2.) Assign server to it

I don't like this method, it doesn't sound that good to me - I want to assign Load Evaluator to server, not other way around.

Problem is that this method works :(

$RequestedLE = New-Object -ComObject MetaFrameCOM.MetaFrameLoadEvaluator
$RequestedLE.LEName = $LoadEvaluator
$RequestedLE.LoadData($True)
$RequestedLE.AttachToServerByName($True, $Server)
$RequestedLE.SaveData()

Martin

Monday, July 14, 2008

Translating books to PowerShell

As mentioned many times, PowerShell is great for one liners.

Currently few blogs are translating books to PowerShell:

Matrix

No Exit

Hamlet

MacBeth

If you follow all posts in time, you can see that scripts are getting smaller and smaller... Well, what about rewriting 1216 pages of The Lord of the Rings? :)

While ($OneRing) {Continue-Journey -Companion (("Frodo", "Sam", "Pippin", "Merry") + $TemporaryHeroes) -Destination "Mount Doom" -Enemy $Sauron -EvilArmy (1..1000000000000)}

Martin

Working with Citrix from PowerShell - custom enumerations

As I continue to work on my PS work flow, time has come to start building Citrix components...

I had tons of vbscripts I wrote before, so this part shouldn't be that hard... One of functions I wanted to have is Get-AppsFromFolder, which can dump published applications from specific folder.

I love this, because than you can have really, really fast enumerations if you want to retrieve published applications based on some filter (for example show me all applications in "applications/primary"). In huge enterprise environments parsing all published icons and filtering output can take ages.

 

In my VBScript I had line
Set rootAppFolder = theFarm.GetRootFolder(MetaFrameAppFolder)

I tried $AppFolder = $CitrixFarm.GetRootFolder(MetaFrameAppFolder). Of course it didn't work - I realized that MetaFrameAppFolder should be index here...

Well, I don't really like $AppFolder = $CitrixFarm.GetRootFolder(12), so I started to write constants. Well, I wrote 2-3 of them and then tried Google :)

I found Citrix MFCom Enums from Brandon Shell and it saved me tons of time - however because I want to implement user-friendly functions and don't want to spend hours and hours by implementing special checks if arguments are correct, I decided that I would rather create custom enumerations.

Then for example if my script requires color as parameter, I can just use simple function:

Function Test-Color([MF.Color]$Color)

and don't need to care about any other code - only accepted values from now on are MF.Color enums...

 

For example I use following to add all supported color values:

New-Enum MF.Color Unknown 16 256 64K 16M

If you would try to use Test-Color with unsupported value, for example Red, you will get following error:

PS C:\Links\HDDs\DevHDD\WorkingProjects\S4Maintenance> Test-Color -Color Red
Test-Color : Cannot convert value "Red" to type "MF.Color" due to invalid enumeration values. Specify one of the follow
ing enumeration values and try again. The possible enumeration values are "Unknown, 16, 256, 64K, 16M".
At line:1 char:18
+ Test-Color -Color  <<<< Red

As you can see, as "side effect" you get all possible enumerations... Also you can use [enum]::GetValues([MF.Color]) to get all possible values.

Right now I can't publish all enumerations I have done, but you can easily translate them from Brandon's excellent blog...

Friday, July 11, 2008

Speed up PowerShell startup times

For long time I am using script Speed-Startup.ps1. It is part of my automated PowerShell installation. I try to keep all my PS-related stuff (scripts, profiles) subversioned - easy to use, free, easy to setup and it already saved me few times.

$NgenLocation = @($(dir -Path (join-path ${env:\windir} "Microsoft.NET\Framework") -Filter "ngen.exe" -recurse) | sort -descending lastwritetime)[0].fullName

If ($(Test-Path -PathType Leaf -Path $NgenLocation)) {
$CurrentAccount = new-object System.Security.Principal.WindowsPrincipal([System.Security.Principal.WindowsIdentity]::GetCurrent())
$Administrator = [System.Security.Principal.WindowsBuiltInRole]::Administrator

If ($CurrentAccount.IsInRole($Administrator)) {
[appdomain]::currentdomain.getassemblies() | ForEach {. $NgenLocation $_.Location}
} Else {
Write-host -ForegroundColor Red "You must be local administrator."
}

} Else {
Write-host -ForegroundColor Red "Ngen.exe was not found"
}




 



You should always run it, because sometimes assemblies are not ngened and PowerShell load is slooooooooow... I just noticed that Jeffrey posted reminder how to speed startup.



I am however curious if there is any way to pre-load .NET environment if you use PowerShell for your logon scripts in Terminal Services\Citrix environment (that means environment where multiple users are logged on to same server).



 



Martin

Returning objects from PowerShell functions

This can be very confusing and I am sure that I will need to describe it to some people in future, so I will rather write small post about it now and just send them link in future ;)

Difference between subroutines and functions is that functions returns some data...

So for example Get-Date that RETURNS date is function. Get-Date that will ONLY display date on screen is subroutine.

If you are used to programming, usually you do something within body function and then you return some object.

Lets have a look at below example. You just run function and it should return "Test" string:

Function Test {
Write-Host 'I want to return object of [String] type with value "Test"'
Return "Test"
}

Makes sense, right? If we run it, we can see following:

PS C:\Links\HDDs\DevHDD\WorkingProjects\S4Maintenance> Test
I want to return object of [String] type with value "Test"
Test

Looks fine so far. Now lets try to assign output from that function to some variable:

PS C:\Links\HDDs\DevHDD\WorkingProjects\S4Maintenance> $Output = Test
I want to return object of [String] type with value "Test"
PS C:\Links\HDDs\DevHDD\WorkingProjects\S4Maintenance> $Output
Test

Still completely normal. But I want to make that function reaaaally really short, so I will remove Write-Host, it's now really needed:

Function Test {
'I want to return object of [String] type with value "Test"'
Return "Test"
}

Still makes perfect sense, right? But it won't work:

PS C:\Links\HDDs\DevHDD\WorkingProjects\S4Maintenance> $Output = Test
PS C:\Links\HDDs\DevHDD\WorkingProjects\S4Maintenance> $Output
I want to return object of [String] type with value "Test"
Test

As you can see both lines are returned... Reason for this is simple:

If you assign output from function to variable, it is NOT object specified after Return command that is returned, but whole output from function.

Only exception (as far as I know) is Write-Host, that is ignored. This behavior can be VERY confusing, because your function can work perfectly, but then suddenly (once you improve it or add more code), it will return System.Object instead of Xml.XmlElement etc.

For workaround, I am passing Output variable name as parameter also:

Function TestOutput ([string]$OutputVariable) {
    Write-Host $OutputVariable
    Set-Variable -Value 0 -Name $OutputVariable -Scope 1
}

$MyVar = 1
Test "MyVar"

As you can see, output is assigned to variable provided as argument. I don't really like this, however I still think it can make your functions more robust. This is specially case with processing XML - some methods will automatically display output from operation to screen and then your output is immediately corrupted.

 

How do you solve this, any ideas???

PowerShell naming conventions

Well, as you probably all know, PowerShell is using verb-noun naming convention.

I really love it. In fact I use it for few years already (Install-Server.cmd, Get-Users.vbs etc).

Obviously I used it only for naming scripts (well, I used it also for functions, but that's different story). Problem with PowerShell is that many names that I really like are already used :( For example New-Object (that's one I really need now ;)).

So I decided to use some kind of namespaces. My current project is called Solution4 Maintenance (short S4M), so I am using
S4M:New-Object or S4M:Move-Object. It is obvious and I can use names that fits me best.

Another advantage for me is that I can easily use
Get-ChildItem Function:\S4M:* to see all functions that are available.

Which naming convention do you use for your scripts? Really curious about it :)

Friday, July 4, 2008

PowerShelling day 2

So I started with my first script finally after spending lot of time studying and designing (mostly designing, I am learning on fly now ;))

Today I learned a lot and also refreshed my memory - my whole day was combination of "Whoa, that's great" and "Grrrrr, doesn't work" ;) I love to learn new stuff and with PowerShell there is lot to learn :)

 

So which problems\solutions I run into? As this is my blog, I also like to use it as reminder ;)

PowerGUI

First of all, I started to use great (free) product PowerGUI. It consists of two user parts - first is visualization of PowerShell (this was promised for new version of MMC, not sure whether it is still the case). This is quite cool, however is not what I am looking for right now.

Second part is really great PowerGUI Script Editor - IDE for PowerShell. I started to use it today and I am already very satisfied, it is really great :)

My bad...

I also run into few problems because I am used to VB.NET programming  - I stuck at one function that simply didn't work correctly. It was supposed to accept 3 parameters, however second and third were always ignored. Skilled powershellers probably already know where was problem - yes, SomeFunction(Param1, Param2, Param3) is array and not 3 standalone arguments :) Ouch, learning something new always hurts, especially if you KNOW about that problem and you just forgot (and then you look at the code and everything seems perfectly normal ;))

Implementing debug log

For my script I wanted to implement debug log. Idea is pretty simple, because it will run as scheduled task, I want to be able to see what happened in case something goes wrong (this is generally problem with scheduled tasks, if they got stuck somewhere, without complex logging there is no way how to find out where or why they got stuck). When I wrote scripts for batches, I used MTee for that purpose. You can then create scheduled task that will automatically generate log file with current console of that script, which is really helpful. In PowerShell, I remembered there was cmdlet Start-Transcript. I used it few years ago for presentations (hmmmmm, maybe that was reason why it was even created ;)). So at beginning of my script I added Start-Transcript -Force -Path $DebugLog. After playing little bit I realized two problems.

PowerShell have by default (when you run script) disabled command echoing (as reminder, this is in batches by default enabled and is configured by @Echo on\off command). This makes complete sense, however it was not perfect for my debug scenario. Leo Tohill (thanks again) pointed me to Set-PSDebug. Parameter -trace 1 enables something very similar to command echoing, it is not exactly what I want (format is quite hard to read in case you have complex script), however it helped me a lot :)

Second problem was mentioned by Shay Levy - transcript outputs ONLY powershell output, so for example if you use ipconfig, you want be able to see it's output in debug log file. As workaround I tried Tee-Object around whole script (S4Maintenance.ps1 | Tee-Object ...). To my surprise situation was opposite - no powershell information, only output from external binaries :D So after a while I came with quite simple solution that works fine so far. I use Start-Transcript and whenever I need to call external binary, I use function Fix-Trascript:

Function Fix-Transcript ()
{
Process{
  Write-Host $_
}
}

For example with ipconfig it means ipconfig | Fix-Transcript. This way output from ipconfig is also stored in transcript file.

Scopes

I decided I want to use scopes also. In batches I implemented a lot of scopes. Not only global\local (SetLocal), but using Set <VarPrefix> behavior of Set command in cmd. For details, have a look at this blog post. By specifying prefix (for example Private.), I was able to automatically destroy all such variables at end (For /f "usebackq tokens=1,* delims==" %%i IN (`Set Private.`) Do Set %%i=).

In PowerShell it is much easier. For my script I would really like to allow fallbacks, which is very hard in batches. You specify some default values and you can override (NOT OVERWRITE) them in sub-scripts or functions. I run into one problem with PowerShell.

Consider example where I want to have (default\global) variable called $JobStorage. This variable must be provided by command line argument. Have a look at following line:

Param (
    [string]$Global:JobStorage = $(throw "You must specify folder where you jobs are stored as parameter."), #Specify folder where jobs are stored

Looks correct, right? If you try to use .\S4Maintenance.ps1 C:\Jobs, it works fine. But I really hate position based parameters - I prefer to use name-based whenever possible. PowerShell automatically supports named parameters, so instead of .\S4Maintenance.ps1 C:\Jobs I can use .\S4Maintenance.ps1 -JobStorage C:\Jobs.

Problem is that if you specify scope, you can't use named parameter. This makes sense and I don't expect that it could be considered bug. As workaround, I assigned it to global variable afterwards:

Param ([string]$JobStorage = $(throw "You must specify folder where you jobs are stored as parameter.")

$Global:JobStorage = $JobStorage

This works as expected.

Background processing

I was also thinking about background processing and run into really nice post here. Highly recommended, I will probably use it once my basic code is finished. BTW background processing is supported in PowerShell v2.

Array index evaluated to null

However of course I got stuck on something :( I use hashtable for some of my entries, but one returns me quite strange error that even Google is not able to help with:

Index operation failed; the array index evaluated to null.
At C:\Links\HDDs\DevHDD\WorkingProjects\S4Maintenance\S4Maintenance.ps1:108 cha
r:19
+                         $Containers[$ <<<< ($Container.Name)] = $Container

Code that I am trying to execute is nothing special from what I can say:

$ContainersToLoad = @{}

        ForEach ($Target in $($Container.failed.targetcontainer)) {
            $ContainersToLoad[$($Target.Name)] = $Target.Name
            }
        ForEach ($Target in $($Container.finished.targetcontainer)) {
            $ContainersToLoad[$($Target.Name)] = $Target.Name
            }
         Write-Host $ContainerToLoad.Count
        If ($ContainerToLoad.Count -gt 0) {
            ForEach ($Target in $ContainersToLoad) {$Target}
        }

 

UPDATE: Of course immediately after I posted it I realized where the problem is (in next minute ;)) one small typo, instead of $ContainersToLoad I used $ContainerToLoad ;) I will probably have a look at strict option in Set-PSDebug tomorrow ;)