*This post originally appeared on the AppSense blog prior to the rebrand in January 2017, when AppSense, LANDESK, Shavlik, Wavelink, and HEAT Software merged under the new name Ivanti.

Blog_Banners_main-page[1]
In my last few posts we have looked at Environment Manager (EM) Configuration scripting, now I would like to show you how the Management Server API’s work and how you can apply them to a real-world scenario.

Anyone who has looked into the Web Services API guide will find that it is particularly well documented in terms of examples provided.   So as not to bore anyone by simply churning the same information out again, I have created a script on this occasion which goes beyond what the documented examples provide.  Throughout this blog I will step you through each command and function so it provides more of a real-world feel.

Before I go on, I should explain that the API set is based upon SOAP requests issued to numerous AMC Web Services.  The methods or functions have been nicely abstracted into a module so they can be easily consumed without having to issue the SOAP requests directly to the server, although I should explain this can still be achieved for those of you who are so inclined.  This means if anyone who has been following my articles around API usage, this set is accessed very differently to say the EM Configuration set for example.

As always the script has been written in a manner to facilitate learning and understanding, so some aspects may appear incomplete or ‘non-optimal’ to the trained eye.

So let’s begin!

What I am going to show you is how to:-

  1. Create a new deployment group
  2. Assign a single endpoint
  3. Assign a package

First things first, we have to specify the path to the web services module and URL of the server we want to connect to:-

[code lang="powershell"]
#  Define Module Path and Server Name
Add-Type -Path "${Env:ProgramFiles}\AppSense\Management Center\Console\ManagementConsole.WebServices.dll"
$url = Read-Host "Enter Server URL eg http://<server>/ManagementServer"
[/code]

Simple enough, next come the authentication part, as per the docs I will use ‘Basic’, however this can be changed:-

[code lang="powershell"]
# Get NetworkCredential instance
$credentials = [System.Net.CredentialCache]::DefaultCredentials
$credential = $credentials.GetCredential($url, "Basic")
[/code]

Now let’s connect:-

[code lang="powershell"]
# Create connection to the Management Server
[ManagementConsole.WebServices]::Connect($url, $credential)
[/code]

Good, that’s the boring part done, let’s look at how we get to the particulars of the API’s.  As I explained earlier these are accessed through web services on the Management Server, so the next logical step to take is to declare variables for each of them.  This’ll save time when we have to call them.  I have not declared all of them as you will see or even utilized them in the script for that matter, however it makes life a little easier if they are there and available.  ‘Intellisense is your friend’, not sure of something or what’s available start typing and IntelliSense will show you the accessible items.  E.g. type [ManagementConsole.WebServices]: As soon as the second ‘:‘ is added Intellisense will show you the available services.

[code lang="powershell"]
#  Define Web Service variables
$AlertsWebService = [ManagementConsole.WebServices]::Alerts
$ConditionsWebService = [ManagementConsole.WebServices]::Conditions
$ConnectedURL = [ManagementConsole.WebServices]::ConnectedUrl
$DatabaseWebService = [ManagementConsole.WebServices]::Database
$DeploymentWebService = [ManagementConsole.WebServices]::Deployment
$DiscoveredMachinesWebService = [ManagementConsole.WebServices]::DiscoveredMachines
$EventsWebService = [ManagementConsole.WebServices]::Events
$GroupsWebService = [ManagementConsole.WebServices]::Groups
$LicensesWebService = [ManagementConsole.WebServices]::Licenses
$MachinesWebService = [ManagementConsole.WebServices]::Machines
$PackagesWebService = [ManagementConsole.WebServices]::Packages
$ProductsWebService = [ManagementConsole.WebServices]::Products
$ReportsWebService = [ManagementConsole.WebServices]::Reports
$SecurityWebService = [ManagementConsole.WebServices]::Security
$ServersWebService = [ManagementConsole.WebServices]::Servers
[/code]

Additionally I have created variables for each of the product’s denoting their static GUID’s.  Not a requirement, again, they can be retrieved by calling a few API calls, however my view is “why make a needless call when you know them already?”

[code lang="powershell"]
#  'Known' Product Codes
[guid]$DataNow = '97C83CBA-28E0-40A2-9150-23C4403518FD'
[guid]$EM = 'EA13F465-B2B6-4716-ADA4-53D7D84D042F'
[guid]$PM = '969D739C-F6BA-4F8E-A210-632FC2B97D70'
[guid]$EMPS = '6E64009E-C563-4C18-A35F-6AC2C704B46A'
[guid]$AM = 'A1FD1D32-4F66-4958-9A81-9B72053661B2'
[guid]$AMC = 'C30C105F-9961-41EA-8DD8-9DF4606E56FB'
[guid]$LIC = '100F5DD5-6C85-4037-89EE-B71A7A498C01'
[guid]$INS = '69EE23B0-E5DC-4294-B417-E12FFA39245A'
[/code]

Next come the main functions.  **Remember PowerShell is not compiled, it is ran line by line so you cannot call a function further down the code as it has not be read yet!

The first of which is the ‘CreateDeploymentGroup’ function.  I wonder what this does J.  The declaration is simple and I like to track the function I am in, particularly if repeat sub-functions are used, so I have created a variable to keep note of it.

[code lang="powershell"]
#  Function CreateDeploymentGroup
{
$function = "CreateDeploymentGroup”
}
[/code]

Jumping to the end of this function, the API we need to call is called ‘CreateGroup’.  Remember the Web Service variables created earlier.  To access this we need to call on the $GroupsWebService variable.  With the help of intellisense if we add a full stop ‘.’ to the end of the variable, all of the accessible API’s will be shown.  In this case, hovering over ‘CreateGroup’ will list the parameters needed to successfully call it.  Unfortunately as deployment groups are very configurable, it takes a large number of parameters, therefore the majority of this function call is purely logic ensuring the correct input is either translated into correct type or incorrect input such as typo’s are handled.

[code lang="powershell"]
#  Create a Deployment Group
$GroupsWebService.CreateGroup([guid]::NewGuid(), $dgName, $dgDescription, $dgPollPeriodSecs, $dgUploadPollPeriodSecs,
$dgEventLogEnabled, $dgFileLogEnabled, $dgFileLogFilename, $dgAnonymousUserLogging, $dgAnonymouysMachineLogging,
$dgOverrideServerURL, $dgSelfRegistrationEnabled, $dgSelfUnregistrationEnabled, $dgSelfUpdateEnabled, $dgPriority,
$dgPollPeriodVariationSecs, $dgUploadPollPeriodVariationSecs, $dgNativeConfigurationEnabled, $dgNativeConfigurationPath)
[/code]
NOTE: above code has been entered on multiple lines due to formating

With such a large amount of parameters, it is typical to ask for user-input.  I have done this in the form of several question prompts during runtime, as you can see there is a reasonable amount of logic placed in the function to handle any incorrectly presented values:-

[code lang="powershell"]
#  Settings
$dgName = Read-Host "Enter Deployment Group Name "
if ($dgName -eq $null -or $dgName -eq "")
{
write-host -ForegroundColor Yellow "No value entered.  Defaulting to 'New Group'."
$dgName = "New Group"
}
$dgDescription = Read-Host "Enter Deployment Group Description "
$dgPollPeriodSecs = Read-Host "Enter the Poll Period in Seconds. (Default 3600) "
if ($dgPollPeriodSecs -ieq $NULL -or $dgPollPeriodSecs -ieq "")
{
$dgPollPeriodSecs = 3600
}
$dgUploadPollPeriodSecs = Read-Host "Enter the Upload Poll Period in Seconds. (Default 1800) "
if ($dgUploadPollPeriodSecs -ieq $NULL -or $dgUploadPollPeriodSecs -ieq "")
{
$dgUploadPollPeriodSecs = 1800
}
$dgEventLogEnabled = Read-Host "Enable Event Logging? True or False  "
$dgEventLogEnabled = TrueorFalse $dgEventLogEnabled $function
[/code]

Note the TrueorFalse function which translates the string “True” or “False” into the Boolean $true or $false which is required by some of the parameters.  This accepts the $function variable for input.  Although in this case I do not use the variable, if the function needed restarting then it can be easily called upon again without significant code re-working.  This function is also key to handling invalid values.  If the value is not “True” or “False”, it will automatically default to $false to handle it.

Next comes the AssignSingleMachine function.  This utilized a few API calls to ensure the correct values are retrieved.  You will find that many of the API calls rely on GUID’s being presented rather than strings, this prevents any ambiguity in the call and ensures uniqueness of the parameters.  The downside to this is that further calls need to be made to retrieve the required GUID’s.  Jumping forward again, to add a machine to a deployment group, it is not an ‘Add’ function, it will be a move.  Remember that any machine in the Management Server will already be in a group, even if it is (default).  Therefore we have to move the machine from one group to another.  Looking at the final call again it requires 3 parameters:-

  • The source group GUID
  • The destination group GUID
  • The target machine GUID

This means we have to access the $GroupsWebService again to establish the source and destination groups and secondly we have to access the $MachinesWebService to work out the machine name and it associated key.  Luckily using some results filtering we can retrieve the GUID’s with ease.  Essentially based upon the use input for the user group, use the ‘GetDeploymentGroupsLight’ API call to retrieve the Groups where the name matches the user-input.  This is used to specify the destination deployment group:-

[code lang="powershell"]
$dgDestination = Read-Host "Please select a Deployment Group"
$dgDestinationKey = $GroupsWebService.GetDeploymentGroupsLight().Groups | where-object {$_.Name -eq $dgDestination} | select GroupKey
[/code]

Next we have to work out which machine we need to move and what deployment group it is currently in.  In this instance the ‘FindMachines’ API is used.  The ‘%’ denotes a wildcard, so it is effectively getting a list of all machines and applying a where clause to match the user-input.  This is done three time to get the machine key, source deployment group name and finally the source deployment group key.

[code lang="powershell"]
$epTarget = Read-Host "Please select a Machine to add to the group"
$epTargetKey =  $MachinesWebService.FindMachines('%').Machines | where-object {$_.NetBiosName -eq $epTarget} | select MachineKey
$dgSource = $MachinesWebService.FindMachines('%').Machines | where-object {$_.NetBiosName -eq $epTarget} | select GroupName
$dgSourceKey = $MachinesWebService.FindMachines('%').Machines | where-object {$_.NetBiosName -eq $epTarget} | select GroupKey
[/code]

Finally we make the ‘Move’ call to move the machine from one group to another:-

[code lang="powershell"]
$MachinesWebService.Move($dgSourceKey.GroupKey, $dgDestinationKey.GroupKey, $epTargetKey.MachineKey)
[/code]
Once more I call the ‘FindMachines’ API to ensure the move has complete successfully.

[code lang="powershell"]
$MachinesWebService.FindMachines('%').Machines | where-object {$_.NetBiosName -eq $epTarget} | select -property @{N='Deployment Group';E={$_.GroupName}}
[/code]

The last function is the ‘AssignSinglePackage’, which is the most complex I am covering here.  Primarily this is due to the large potential of matches and types for that matter you can hit, meaning multiple API calls are required to meet the requirements.  Arguably this may be viewed as a limitation of the API that there is no simple ‘Find’ base mechanism, such as the ‘FindMachine’ API.

In this example I have required the end-user to provide input so that the package list can be narrowed down.  The requirement is based upon Product and Build Version to whittle the list down to a handful of packages.  Again filtering is used against the API results to filter the result set.  The Key API’s used are ‘GetPackageFromProductName’, passing in the user input and then it is repeated with the version number as an extra filter in the where clause.

[code lang="powershell"]
$pacTargetProduct = Read-Host "Please enter the target product e.g. Environment Manager"
$pacTargetVersion = Read-Host "Please enter the target build version e.g. 8.6.313"
$PackagesWebService.GetPackageFromProductName($pacTargetProduct).PackageVersions | where {$_.Version -like "$pacTargetVersion*"} | select Name, Version | ft
$pacTargetName = Read-Host "Please enter the package name"
$pacTargetKey = $PackagesWebService.GetPackageFromProductName("$pacTargetProduct").PackageVersions | where {($_.Version -like "$pacTargetVersion*") -and ($_.Name -eq $pacTargetName)} | select PackageKey
$dgDestinationKey = $GroupsWebService.GetDeploymentGroupsLight().Groups | where-object {$_.Name -eq $dgDestination} | select GroupKey
$GroupsWebService.AddGroupLatestPackage($dgDestinationKey.GroupKey, $pacTargetKey.PackageKey, [ref]$date)
[/code]

Finally the ‘AddGroupLatestPackage’ API is used to add the selected package to the required deployment group.  3 parameters are utilized again the:

  • Deployment group GUID
  • Package GUID
  • Modified Date Stamp

It is worth nothing this approach is flawed in that it requires a build number to be known e.g. 8.6.313 otherwise just putting 8.6 in may return multiple packages with the same name, which results in multiple GUIDs being returned however this example is just to show the utilization.

Well that’s the end of the blog, as always feels free to comment further.  Please download my script and have a play.  There’s lots more to explore in this API set, I have barely scratched the surface!  Happy Scripting!