So, I promised you a part 2 in this series. This time, I want to deep-dive more into real-world usable PowerShell (PoSh) code. I will show you how my “framework” around XenMobile scripts work, and provide a link to scripts that I have written, that are known to work in the environments I have tested. As usual, when handling scripts, there is a disclaimer:

I have tested these scripts in my lab environment, and in some customer environments. They have not been tested for reliability, security issues, or anything else. These scripts are provided by me personally, and not by Citrix Systems, Inc. As such, I cannot provide any form of guarantee, and if your environment breaks because of these scripts, I cannot be held responsible. They are just examples, that happen to work perfectly in the (very few) environments where I have tested them.

So, let’s kick off some real scripting here! My first example will be my “ListSecureAppUsers.ps1”. This script will list all devices who have installed Secure Mail / Secure Web or ShareFile.

The first section I use, is what PowerShell uses as the “help” text for the script. Although not strictly required, you will be happy to see it if you find the script in a few months, and don’t remember how to use it!

<#
 .SYNOPSIS
 .
 .
 .
 #>

An explanation of this section can be found here: https://technet.microsoft.com/en-us/library/ff458353.aspx

Next up, I define the parameters to use for invocation of the script:

param (
 [parameter (mandatory=$true)][string]$xmhost,
 [parameter (mandatory=$true)][string]$user,
 [string]$password,
 [string]$csvfile="SecureAppsInstalled.csv"
 )

Here, we can see that we have four parameters. The “xmhost” and “user” are mandatory. This means that if not supplied on the command line, the user will be asked for them. We don’t set “password” as mandatory, since we want to ask for that in a more secure way. “CSVfile”, the name of the CSV file to create, has a default value of “SecureAppsInstalled.csv”, which will be used if not specified on the command line.

My next section in the script, is for functions. However, I currently only use one function, ConvertTo-Plaintext:

Function ConvertTo-PlainText( [security.securestring]$secure ) {
 $marshal = [Runtime.InteropServices.Marshal]
 $marshal::PtrToStringAuto( $marshal::SecureStringToBSTR($secure) )
 }

This function will take the password string that we will read from the console, and convert it to a usable string.
$BASEURI="https://" + $XMHOST + ":4443"

# If password has not been input through command line, let's ask for it in a secure way!
 if ($password.Length -eq 0)
 {
 $SecPass = Read-Host -assecurestring "Please enter your password"
 $PlainPass=ConvertTo-Plaintext($SecPass)
 }
 else
 {
 $PlainPass=$password
 }

Our next piece of code asks for the password of the user in a secure way. If you (for security reasons) do not want to input the password in the command line, we now ask for it, masking your input. We then call the function we created above, to convert the password to plain text (in memory).

So, we have all the data we need, now it is time to start using REST API. So, this is where the script itself starts:

# Preparing variables to be used for the initial request
$count=0

$headers = @{
"Content-Type" = "application/json"
}

$json = "{
        ""login"":""" + $User + """,
        ""password"":""" + $PlainPass + """
        }"

#Get credential token
$URI=$BASEURI + "/xenmobile/api/v1/authentication/login"

$authtoken=try{
    Invoke-RestMethod -Headers $headers -Method Post -Uri $URI -Body $json
    } catch { $_.Exception }

    if ($authtoken.auth_token -eq $null)
    {
        if ($authtoken.Response.StatusCode -ne $null)
        {
            if ($authtoken.Response.StatusCode.Value__.Equals(401) -or $authtoken.Response.StatusCode.Value__.Equals(400))
            {
                Write-Output "Bad username or password, unauthorized!"
            }
        }
        
        else
        {
            Write-Output "Error! Errormessage: " $authtoken.Message
            Write-Output $authtoken.InnerException
        }
    exit
    }
    
# If we have come this far, we have a token ($authtoken.auth_token is not NULL)
$token=$authtoken.auth_token
$headers.add("auth_token",$token)


Here, we first define the “count” variable, which tells us how many devices have been read so far. We then move on to prepare the “Header” array for the REST request. Finally, we also create the body for the authentication request, and perform the REST call. You can see that I store the output of the REST call in the variable “$authtoken”. At this stage, since PowerShell speaks JSON natively, the actual authentication token is stored in “$authtoken.auth_token”.

Now, I initialize some data:

# Token has been added to the header information. From here on, we are authorized to do what we need to do!
$URI=$BASEURI + "/xenmobile/api/v1/device/filter"
$body = '{
        "start": 0,
        "limit": 200
        }'

$results = @()
$inactive = @()
$count=200
$devicesRead=0
$numberFound=0

Here, we define which URI to connect to for XenMobile. In this instance, we want to list all devices. We will use the “filter” function, but the filter we be left empty. As mentioned in Part 1 of this blog series, for large environments, XenMobile will only give you a certain amount of data at a time. So we define the body to start at element number 0, and give us a total count of up to 200 devices. I also set the “$count” parameter to 200, since I will now go into a “while-loop”, which is supposed to run for as long as the query returns a count of 200 devices. The logic here, is that I can now add 200 to “start” for each run, and when there are no more devices left in the database to report on, the query, with a given “start”, will yield fewer than 200 devices, and we will exit the loop.
while ($count -eq 200)
{
    $devices=Invoke-RestMethod -Headers $headers -Method Post -Uri $URI -Body $body
    foreach ($device in $devices.filteredDevicesDataList)
    {
        $devicesRead+=1
        $inactivityDays = [convert]::ToInt32($device.inactivityDays, 10)
        if ( $inactivityDays -lt 90 )
        {
            $AppURI=$BASEURI + "/xenmobile/api/v1/device/" + $device.id + "/apps"
            #Write-Host "Checking URI: " + $AppURI
            $body = '{}'
            $apps=Invoke-RestMethod -Headers $headers -Method Get -Uri $AppURI
            foreach ($app in $apps)
            {
                foreach ($curApp in $app.applications)
                {
                    if (($curApp.PackageInfo -like "*mail*" -or $curApp.PackageInfo -like "*sharefile*" -or $curApp.PackageInfo -like "*browser*") -and $curApp.status -eq "SUCCESS")
                    {
                        $numberFound += 1
                        $data = [ordered] @{
                            "User" = $device.userName
                            "Device Model" = $device.deviceModel
                            "Device Name" = $device.deviceName
                            "Device Serial" = $device.serialNumber
                            "Device IMEI" = $device.imeiOrMeid
                            "OS Version" = $device.OSVersion
                            "Platform" = $device.platform
                            "Inactivity" = $device.inactivityDays
                            "App Name" = $curApp.name
                            "App Package" = $curApp.PackageInfo
                            }
                        $results += New-Object PSObject -Property $data
                    }
                }
            }
        }
    }
    $count=$devices.filteredDevicesDataList.count
    $devicesRead+=$count
    $body = '{
        "start": ' + ($devicesRead) + ',
        "limit": 200
        }'
}

Here is the actual While-loop. So, to find out which devices actually have XenMobile Apps installed, for each device being listed, I make a new call, and retireves a list of all applications installed. At this point, I could have just reported all apps installed, but as this was for a real customer case, where the customer wanted to know which devices had Secure Apps, I only store data about the device and the app if it is Secure Mail, ShareFile, or Secure Web. Note that the API used here,”/xenmobile/api/v1/device/<device ID>/apps”, lists apps that have been installed through XenMobile. If you want the entire inventory of apps, you would need to replace the “/apps” ending in the URI with “/softwareinventory”.

Note also, how I create an ordered array of data for each device, and adds this array as an object into the variable “$results”. This is done to make it easy to export all the data to a CSV file as soon as the script has run.

At the end of the while-loop, you can see how we update the number of devices we have already read, and the “start” parameter of the JSON body.

After this, there is just one line of data (plus one comment) left in my code:

# All data has been inserted into the "$results" array, and can now be output into a nice CSV
$results | export-csv -path $csvfile -NoTypeInformation

Here, the entire result of the report is exported into a CSV file.

I have now used this script, and several variants of this script to create different reports, and also to make simple changes, like setting all devices to “Corporate”. It would also be easy to modify in order to select specific devices to be set as “Corporate” or “Personal”. As mentioned in Part 1 of this blog series, you can use the output from “/devices/filter” to see which filters can be set, and to add filters to the JSON body of your REST call. Obviously, if you want a report created and sent on a regular basis, adding some code to send an email, and then running this script as a scheduled task on a Windows Server, would be very trivial. Since this is all REST, you don’t even need any particular SnapIns or Modules in PowerShell!

To download this script, and a few other samples, that work in my environment, and MIGHT work in your environment, click here!

Part 3 of this blog series, which will be the last part, and will hopefully be available in a few months, will show an example of how to build your own enrollment portal in PHP.

XenMobile IDC banner