Login to Storefront with cURL

This is the project that made me want to start documenting some of my scripts & discoveries.

We had an existing client that used their own .NET application to log into their MetaFrame environment (using Web Interface/Secure Gateway). With the impending doom placed upon all Windows Server 2003 servers (and hence our MetaFrame environment), we had a requirement to a) move them to XenApp 7.6 running Server 2012 R2, and b) adapt their current login process to work the same but with Netscaler Gateway/Storefront.

I’d had very little exposure to web technologies at this point, so it was a grind from the start, but some with some help from my friendly development community I was pushed in the right direction… enter amazing new discoveries like Fiddler, CSRF tokens, and cURL. Much much much later, I discovered the official API documentation for the Storefront API…

Now, a little tangent on cURL. I’d heard about it before but being a Powershell lover I’d never really used it – Invoke-WebRequest always did what I needed. In this case however, I discovered that Invoke-WebRequest didn’t seem to handle cookies quite the same as cURL. Plus – the client’s example script they sent us was using cURL – so I had to sent it back using cURL again, but utilising Storefront rather than Secure Gateway.

DISCLAIMER: This script was polished up a little by another developer. You know who you are.

So. Let’s start with the full script. As mentioned, this was scripted to login to our Netscaler Gateway, with passthrough authentication to the Storefront. Direct login to Storefront is also possible and easy, but not directly covered in this script.

https://desktop.saaas.com is the URL of our Netscaler Gateway vServer.


$curlPath = "C:\cURL"
$outputFile = "$curlPath\Login.txt"
$cookieJar = "$curlPath\cookie.txt"
$icaProg = "C:\Program Files (x86)\Citrix\ICA Client\wfica32.exe"
$deliveryGroupName = "SysAdmin As A Service Desktop"
$username = "user@saaas.com"
$password = "MyPassword"

$step = 1





.\curl.exe  --cookie-jar $cookieJar
--output "$($curlPath)\OUTPUT1.txt" `
--data "login=$($username)&passwd=$($password)" `
`
--header 'Accept: text/html, application/xhtml+xml, image/jxr, */*' `
--header 'Referer: https://desktop.saaas.com/vpn/index.html' `
"https://desktop.saaas.com/cgi/login"</div>

# 2. /home/configuration - Get CSRF Token & ASP Session ID
$step = 2
.\curl.exe --request POST --location --cookie-jar $cookieJar --cookie $cookieJar
--output "$($curlPath)\OUTPUT2.txt" `
--dump-header "$($curlPath)\CSRF-Token.txt" `
--cacert "$($curlPath)\curl-ca-bundle.crt" `
--header 'Accept: application/xml, text/xml, */*; q=0.01'`
--header 'Content-Length: 0'`
--header 'X-Citrix-IsUsingHTTPS: Yes'`
--header 'Referer: https://desktop.saaas.com/Citrix/StoreWeb/'`
"https://desktop.saaas.com/Citrix/StoreWeb/Home/Configuration"

# 3. Find CSRF Token
$step = 3




$headers = Get-Content "$($curlPath)\CSRF-Token.txt" | Select-String "Set-Cookie: CsrfToken="
$csrfToken = ($headers -split "=" -split ";")[1]
#echo ($csrfToken)</div>




# Storefront GetAuthMethods - must do this before login
.\curl.exe --request POST --cookie-jar $cookieJar --cookie $cookieJar --output "$($curlPath)\OUTPUT3.txt" `
--header 'Accept: application/xml, text/xml, */*; q=0.01'`
--header "Csrf-Token: $($csrfToken)"`
--header 'X-Citrix-IsUsingHTTPS: Yes'`
--header 'Referer: https://desktop.saaas.com/Citrix/StoreWeb/'`
--header 'Content-Length: 0'`
"https://desktop.saaas.com/Citrix/StoreWeb/Authentication/GetAuthMethods"




# 4. storefront login
$step = 4




.\curl.exe --request POST --cookie-jar $cookieJar --cookie $cookieJar `
--header 'Accept: application/xml, text/xml, */*; q=0.01'`
--header "Csrf-Token: $($csrfToken)"`
--header 'X-Citrix-IsUsingHTTPS: Yes'`
--header 'Referer: https://desktop.saaas.com/Citrix/StoreWeb/'`
--header 'Content-Length: 0'`
"https://desktop.saaas.com/Citrix/StoreWeb/GatewayAuth/Login"</div>




# 5. List resources
$step = 5




.\curl.exe --request POST --cookie-jar $cookieJar --cookie $cookieJar  `
--output "$($curlPath)\Resources.json" `
`
--header 'Content-Type: application/x-www-form-urlencoded; charset=UTF-8'`
--header 'Accept: application/json, text/javascript, */*; q=0.01'`
--header "Csrf-Token: $($csrfToken)"`
--header 'X-Citrix-IsUsingHTTPS: Yes'`
--header 'Referer: https://desktop.saaas.com/Citrix/StoreWeb/'`
--data "format=json&resourceDetails=Default" `
"https://desktop.saaas.com/Citrix/StoreWeb/Resources/List"




$j = (Get-Content "$curlPath\Resources.json" -Raw) | ConvertFrom-Json
$desktopDeliveryGroup = $j.resources | where {$_.name -eq "Sysadmin As A Service Desktop"}




# 6. Launch URL
$step = 6




.\curl.exe --request GET --cookie-jar $cookieJar --cookie $cookieJar `
--output "$($curlPath)\launch.ica"`
"https://desktop.saaas.com/Citrix/StoreWeb/Resources/LaunchIca/$($desktopDeliveryGroup.id).ica?CsrfToken=$($csrfToken)&IsUsingHttps=Yes"





# 7. Launch Desktop
$step = 7




Start-Process "$($curlPath)\launch.ica"


Ok, now let’s break it down into steps.
Step 1: Login to Netscaler Gateway
This is pretty straightforward – just pass the username & password in the data portion of cURL, and store the cookie in a file.


.\curl.exe  --location --cookie-jar $cookieJar
--output "$($curlPath)\OUTPUT1.txt" `
--data "login=$($username)&passwd=$($password)" `
`
--header 'Accept: text/html, application/xhtml+xml, image/jxr, */*' `
--header 'Referer: https://desktop.saaas.com/vpn/index.html' `
"https://desktop.saaas.com/cgi/login"


Step 2: Get CSRF Token & ASP.NET session ID
This step is pretty important – it’s the first call to our Storefront server, and when we get the CSRF token and ASP.NET session ID. Without these passed into every subsequent call to Storefront, you’ll get a 403 Forbidden response.


<div>.\curl.exe --request POST --location --cookie-jar $cookieJar --cookie $cookieJar
--output "$($curlPath)\OUTPUT2.txt" `
--dump-header "$($curlPath)\CSRF-Token.txt" `
--cacert "$($curlPath)\curl-ca-bundle.crt" `
--header 'Accept: application/xml, text/xml, */*; q=0.01'`
--header 'Content-Length: 0'`
--header 'X-Citrix-IsUsingHTTPS: Yes'`
--header 'Referer: https://desktop.saaas.com/Citrix/StoreWeb/'`
"https://desktop.saaas.com/Citrix/StoreWeb/Home/Configuration"</div>


Step 3: Store the CSRF token in a new variable
This takes the response from Step 2 and stores the CSRF token in a new variable.



$headers = Get-Content "$($curlPath)\CSRF-Token.txt" | Select-String "Set-Cookie: CsrfToken="
$csrfToken = ($headers -split "=" -split ";")[1]
#echo ($csrfToken)


Step 3b: Get Authentication Methods from Storefront
Although we know what Authentication method we want to use to log into the Storefront (passthrough from Netscaler Gateway), we still need to initiate GetAuthMethods before Storefront will be ready for us to send a login request.


.\curl.exe --request POST --cookie-jar $cookieJar --cookie $cookieJar --output "$($curlPath)\OUTPUT3.txt" `
--header 'Accept: application/xml, text/xml, */*; q=0.01'`
--header "Csrf-Token: $($csrfToken)"`
--header 'X-Citrix-IsUsingHTTPS: Yes'`
--header 'Referer: https://desktop.saaas.com/Citrix/StoreWeb/'`
--header 'Content-Length: 0'`
"https://desktop.saaas.com/Citrix/StoreWeb/Authentication/GetAuthMethods"


Step 4: Login to Storefront
Finally, we can login to the Storefront by passing our cookie with our NSC_AAAC token to the Storefront server.

.\curl.exe --request POST --cookie-jar $cookieJar --cookie $cookieJar `
--header 'Accept: application/xml, text/xml, */*; q=0.01'`
--header "Csrf-Token: $($csrfToken)"`
--header 'X-Citrix-IsUsingHTTPS: Yes'`
--header 'Referer: https://desktop.saaas.com/Citrix/StoreWeb/'`
--header 'Content-Length: 0'`
"https://desktop.saaas.com/Citrix/StoreWeb/GatewayAuth/Login"


Step 5: List Resources
Now, we request a list of all available resources (Delivery Groups & Published Apps) from the Storefront server. We’ll get back a JSON file with names, IDs and launch URLs. Then, we parse the output to select the resource name of our chosen Delivery Group.


.\curl.exe --request POST --cookie-jar $cookieJar --cookie $cookieJar  `
--output "$($curlPath)\Resources.json" `
`
--header 'Content-Type: application/x-www-form-urlencoded; charset=UTF-8'`
--header 'Accept: application/json, text/javascript, */*; q=0.01'`
--header "Csrf-Token: $($csrfToken)"`
--header 'X-Citrix-IsUsingHTTPS: Yes'`
--header 'Referer: https://desktop.saaas.com/Citrix/StoreWeb/'`
--data "format=json&resourceDetails=Default" `
"https://desktop.saaas.com/Citrix/StoreWeb/Resources/List"

$j = (Get-Content "$curlPath\Resources.json" -Raw) | ConvertFrom-Json
$desktopDeliveryGroup = $j.resources | where {$_.name -eq $deliveryGroupName}


Step 6: Get Launch.ica file
This is where we request the ICA file of our chosen Delivery Group and save the output as launch.ica


.\curl.exe --request GET --cookie-jar $cookieJar --cookie $cookieJar `
--output "$($curlPath)\launch.ica"`
"https://desktop.saaas.com/Citrix/StoreWeb/Resources/LaunchIca/$($desktopDeliveryGroup.id).ica?CsrfToken=$($csrfToken)&IsUsingHttps=Yes"


Step 7: Launch!
Finally, we launch Citrix using wfica32.exe and our launch.ica file


Start-Process "$($curlPath)\launch.ica"

Congratulations! You have now logged into & launched a Citrix session using the Storefront API.
For full details on the API documentation – see Citrix SDK Page – you will need a Citrix login to access this 🙂

Logitech MX Master

Yep, it was totally worth switching to this mouse.

For starters, the tracking isn’t weird like on the M705. It is dead on… I feel like the cursor is just an extension of my hand…

Next, the larger profile, added weight and slightly more aggressive lean angle on the mouse make it a more comfortable fit.

The scroll wheel has the same two-mode feature – either click-scroll or free-wheeling. However, on the MX Master the button you press to switch modes is more flush to the body of the mouse, and only requires a slight click, whereas on the M705 I felt I had to stretch my finger up to press the button in – it just didn’t feel easy. Secondly on the scroll whell, the scroll button (click the scroll button for middle click) is a lot easier than on the M7o5 – which again, felt more spongy and required more action. It also meant it was difficult to use middle click without accidentally scrolling.

It has an inbuilt battery with USB charging cable, so no more replacing batteries! I’m not sure what the battery life is like yet.

The only thing I found slightly better about the M705 is the thumb-wing button. It was a lot ‘clickier’ on the M705, a bit spongy and hard to find on the MX Master. SetPoint / Logitech Options seems to provide good options for changing the function.

The MX Master also has some cool horizontal scroll wheels next to the back/forward buttons on the mouse (which I haven’t got working yet). It does make the back/forward buttons smaller and a little harder to use though.

Lastly, the MX Master has both Bluetooth and Unifying Receiver compatibility. A kick-ass feature for me as it can connect to both my tablet (Bluetooth) or my laptop (Receiver). I use Logitech gear where possible because of the Unifying Receiver, but this makes it even better for when I’ve lost the stupid little dongle or don’t have any spare full-sized USB ports!

All in all, I would 100% recommend you buy this mouse over the M705 (or even the M905). The MX Master was about 50% more expensive, but I’m using it every day, all day… totally worth the extra money.

Logitech M705 Marathon

I needed a new mouse, having used a basic Logitech one for yonks, and I was starting to get a sore arm by the end of the day. I’ve tried the Logitech T650 touchpad before (more for gimmicks than anything else), and while that was nice it was difficult to use under pressure.

So, shopping time! Most of the guys in the office have the Logitech Performance MX, but I had a gift voucher for a local retailer who unfortunately didn’t stock that… I checked their website almost weekly for any new stock and was delighted to see the Marathon M705 appear. Seemed like everything I wanted – unifying receiver, contoured grip, custom buttons, wireless, and reasonable money!

Then, I started to read reviews. Oh, Google.

Seems like there was two camps – some people loved the mouse as a more affordable alternative to the Performance MX, and those who absolutely hated the right-justified tracking laser.

After 30 minutes using this mouse, I think I’m in camp two.

The reason the right-justified laser is such a problem is because of the way that most right-handed people use their mouse – pivoting from the wrist. So, a slight movement to the left will move the cursor slightly, but the more you move it, the cursor will move exponentially more. It’s weird, you have to try it out to see.

Everything else about the mouse is brilliant – great grip, battery life is excellent (in the 30 minutes I’ve been using it…), unifying receiver is great. But I’m very glad I kept the receipt as I’ll be returning it to swap for the MX Master that was on sale there!

So, if you’re looking at buying this mouse, I wouldn’t say DON’T DO IT… but try get a demo of the mouse first, see if the shop has one you can plug in and test.

I’ll try update this post if/once I’ve swapped for the MX Master or something else.

UPDATE: Swapped it for a better mouse! Read my review here

QUSER and PowerShell

UPDATED! with better Date format handling and added in Idle Time and shortened it and better coding in general. But still using QUSER 🙂

Quser is great. So is qwinsta/rwinsta and tsdisconn. But unfortunately they’re not super-simple to parse the output when using PowerShell.

I’ve seen some examples of Quser > Powershell conversion scripts before (I think splitting on “\r\n” or similar, ) but I decided to make my own…
In retrospect it would probably be a lot easier to do this using WMI, but hey.

I’ve added the ability to check against remote systems as well, but it does require you having the regular remote RPC calls that quser uses (correct me if I’m wrong on that one).

The more I review this, the more I want to clean it up (e.g., use WMI, use array splicing to add in the null SessionName rather than if/else… etc.). But it works, and at this point I need to move on…

Update: have moved the script to a GitHub repo for easier contributions. If someone knows how to add a GitHub repo to a WordPress page easily… let me know!

View Script on Github

function Get-LoggedOnUsers ($server) {

if($server -eq $null){
	$server = "localhost"
}

$users = @()
# Query using quser, 2>$null to hide "No users exists...", then skip to the next server
$quser = quser /server:$server 2>$null
if(!($quser)){
	Continue
}

#Remove column headers
$quser = $quser[1..$($quser.Count)]
foreach($user in $quser){
	$usersObj = [PSCustomObject]@{Server=$null;Username=$null;SessionName=$null;SessionId=$Null;SessionState=$null;LogonTime=$null;IdleTime=$null}
	$quserData = $user -split "\s+"

	#We have to splice the array if the session is disconnected (as the SESSIONNAME column quserData[2] is empty)
	if(($user | select-string "Disc") -ne $null){
		#User is disconnected
		$quserData = ($quserData[0..1],"null",$quserData[2..($quserData.Length -1)]) -split "\s+"
	}

	# Server
	$usersObj.Server = $server
	# Username
	$usersObj.Username = $quserData[1]
	# SessionName
	$usersObj.SessionName = $quserData[2]
	# SessionID
	$usersObj.SessionID = $quserData[3]
	# SessionState
	$usersObj.SessionState = $quserData[4]
	# IdleTime
	$quserData[5] = $quserData[5] -replace "\+",":" -replace "\.","0:0" -replace "Disc","0:0"
	if($quserData[5] -like "*:*"){
		$usersObj.IdleTime = [timespan]"$($quserData[5])"
	}elseif($quserData[5] -eq "." -or $quserData[5] -eq "none"){
		$usersObj.idleTime = [timespan]"0:0"
	}else{
		$usersObj.IdleTime = [timespan]"0:$($quserData[5])"
	}
	# LogonTime
	$usersObj.LogonTime = (Get-Date "$($quserData[6]) $($quserData[7]) $($quserData[8] )")

	$users += $usersObj

}

return $users

}

 


Thanks

Citrix Profile Manager – IE Saved Passwords

A winner first post… Internet Explorer not remembering Forms-based (i.e., AutoComplete) passwords for a website after the Citrix VDA has been restarted, or if I log into a different server.

This was a real pain for some of my users that I didn’t pick up on during the testing phase of deploying UPM (because I don’t generally save passwords for websites I go to, I remember them… but that’s another story) and caused a few swears & tears when it took me a few days to resolve.

Internet Explorer (I’m working with v11 and up on Server 2012 R2, XenApp 7.6 published desktops, UPM 5.2) remembers passwords in two ways – the Username/Password popup box that it displays when presented with an HTTP 401 challenge – these are stored as Generic Credentials in the Windows Credential Manager.

The other way it can store passwords is via an AutoComplete form.

According to Nirsoft, the HTTP Auth passwords are stored in AppData\Roaming\Credentials and the forms-based auth passwords are stored in HKEY_CURRENT_USER\Software\Microsoft\Internet Explorer\IntelliForms\Storage2. I couldn’t quite match this up with what was happening in my environment, but using this post (and after restarting my VDAs) I was able to get the “Web Credentials” section of Credential Manager to roam to different servers & persist after a reboot.

In summary, configure the following Citrix Profile Manager policies:

Directories to Synchronize:

AppData\Local\Microsoft\Credentials

Folders to Mirror:

AppData\Local\Microsoft\Vault

Process Internet Cookies at Logoff:

Enabled