Posted on 09-23-2016 12:34 PM
I've had a few instances where I needed to clone and rename a smart computer groups across many sites where doing them 1 by 1 would be tedious (as recommended here: https://jamfnation.jamfsoftware.com/featureRequest.html?id=3349).
I've scripted this process so you can follow a few (moderately error checked) prompts and clone a smart computer group across specific sites of your choice (or all sites). See below PHP code:
<?php
/*
Author: Sean Burke
Date: 9-8-2016
Version: 1.0
Purpose: To give us the ability to copy Smart Computer Groups (SCG) to multiple sites or all sites.
*/
// MODIFY THESE VARIABLES!
$apiUsername = "";
$apiPassword = "";
// Add in the URL for each JSS you may have (production, dev, etc)
$jssURL1 = "";
$jssURL2 = "";
// DO NOT MODIFY
$smartComputerGroupID = "";
$smartComputerGroupName = "";
/*
Generic function to call the JSS and get data back and return it.
*/
function query_jss($jssURL)
{
global $apiUsername;
global $apiPassword;
$ch = curl_init();
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
//curl_setopt($ch, CURLOPT_VERBOSE, 1);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
curl_setopt($ch, CURLOPT_URL, $jssURL);
curl_setopt($ch, CURLOPT_USERPWD, "$apiUsername:$apiPassword");
$json= curl_exec($ch);
return $json;
}
/*
Get all the sites associated with a JSS, add in 999 for cloning to all sites, and sort them.
Return as array.
*/
function get_sites($jss)
{
global $apiUsername;
global $apiPassword;
# Specify the URL for gaining the Sites Information
$url = $jss ."/JSSResource/sites";
$json = query_jss($url);
if (strpos($json,"The server has not found anything matching the request URI"))
{
echo "cURL Error: $ch_error";
}
else
{
$xml = str_replace( '#', '', $json );
$xml = str_replace( '&', '', $xml);
// Machine Attributes we want to retrieve
$nodes = new SimpleXMLElement($xml);
$siteArray = [];
foreach($nodes->site as $siteInfo)
{
$siteName = $siteInfo->name;
$siteName = str_replace("amp;","&",$siteName);
$siteID = (int)$siteInfo->id;
$siteArray[$siteID] = $siteName;
}
// Add 999 to reflect clone to all sites
$siteArray[999] = "Global (Clone to All Sites)";
// Sort / order the array and return
ksort($siteArray);
return $siteArray;
}
}
/*
Check Read access to the API's /computergroups. If we don't have access there, then we have a problem.
*/
function check_auth($jssURL)
{
global $apiUsername;
global $apiPassword;
$jssURL = $jssURL . "/JSSResource/computergroups";
$ch = curl_init();
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
//curl_setopt($ch, CURLOPT_VERBOSE, 1);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
curl_setopt($ch, CURLOPT_URL, $jssURL);
curl_setopt($ch, CURLOPT_USERPWD, "$apiUsername:$apiPassword");
$json= curl_exec($ch);
if (strpos($json,"The request requires user authentication"))
{
// We have an authentication problem. Either no rights or no creds provided.
return 1;
}
elseif(strpos($json,"<computer_groups>"))
{
// We got a valid XML response listing computer groups.
return 0;
}
elseif(empty(trim($json)))
{
// No response implies a wrong server.
return 2;
}
else
{
// Something unexpected happened. Just return the entire HTML result and display to the user.
return "$json";
}
}
/*
Clear the terminal so we can display new dialogs.
*/
function clear_screen()
{
system('clear');
}
/*
Function to clone the smart computer groups. Takes in the following variables
- $verifiedResponseArray - Sites that the user wants to clone the SCG to
- $siteArray - All Sites associted with that JSS
- $jss - The JSS URL
*/
function clone_smart_computer_groups($verifiedResponseArray,$siteArray,$jss)
{
global $apiUsername;
global $apiPassword;
global $smartComputerGroupID;
global $smartComputerGroupName;
$numCriteria = 0;
# Specify the URL for gaining the Sites Information
$url = $jss ."/JSSResource/computergroups/id/$smartComputerGroupID";
$json = query_jss($url);
$xml = str_replace( '#', '', $json );
$xml = str_replace( '&', '', $xml);
// Machine Attributes we want to retrieve
$nodes = new SimpleXMLElement($xml);
// Get Smart Computer Group attributes
$smartComputerGroupName = $nodes->name;
$computerGroupType = $nodes->is_smart;
$criteria = $nodes->criteria;
$scgCriteria = "";
// Loop through all smart computer group criterion
foreach($criteria as $criterion)
{
foreach($criterion as $key => $value)
{
$name = $value->name;
if(!empty($name))
{
$numCriteria++;
$priority = $value->priority;
$and_or = $value->and_or;
$searchType = $value->search_type;
$value = $value->value;
$scgCriteria = $scgCriteria . "<criterion><name>$name</name><priority>$priority</priority><and_or>$and_or</and_or><search_type>$searchType</search_type><value>$value</value></criterion>";
}
}
}
// If the user wants to clone scg data to all Sites
if(in_array("999",$verifiedResponseArray))
{
echo "
########## SMART COMPUTER GROUP CLONER RESULTS ############
";
// Loop through every site listed except 999 since that's not a real site
foreach($siteArray as $siteID => $siteName)
{
if($siteID != 999)
{
$finalscgName = "";
// Get the site name and make sure it's modified to be XML friendly
$siteName = $siteArray[$siteID];
$siteName = htmlspecialchars($siteName, ENT_XML1);
// Modify the scg name to include the site name [Site] - scg Name to be explicit
$finalscgName = "[$siteName] - " . $smartComputerGroupName;
// Form the XML
$smartComputerGroupXML = "<computer_group><name>$finalscgName</name><id>0</id><is_smart>$computerGroupType</is_smart><site><id>$siteID</id><name>$siteName</name></site><criteria>$scgCriteria</criteria></computer_group>";
$url = $jss . "/JSSResource/computergroups/id/0";
$ch = curl_init($url);
#curl_setopt( $ch, CURLOPT_URL, $url );
curl_setopt( $ch, CURLOPT_SSL_VERIFYHOST, 0);
curl_setopt( $ch, CURLOPT_SSL_VERIFYPEER, 0);
curl_setopt( $ch, CURLOPT_POST, 1 );
curl_setopt( $ch, CURLOPT_HTTPHEADER, array('Content-Type: text/xml'));
curl_setopt( $ch, CURLOPT_POSTFIELDS, "$smartComputerGroupXML" );
curl_setopt( $ch, CURLOPT_USERPWD, "$apiUsername:$apiPassword");
curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 );
$result = curl_exec($ch);
curl_close($ch);
// Some error handling in case the uploads go wrong
if (strpos($result,"The server has not found anything matching the request URI"))
{
echo " FAILED: Smart Computer Group: $finalscgName was NOT created because the requested URL $url was not found!
";
}
elseif(strpos($result,"Error: Duplicate name"))
{
echo " DUPLICATE: Smart Computer Group: $finalscgName was NOT created because that Smart Computer Group name alread exists!
";
}
else
{
$xml = str_replace( '#', '', $result );
$xml = str_replace( '&', '', $xml);
$nodes = new SimpleXMLElement($xml);
$newSCGID = $nodes->id;
echo " RESULT: Smart Computer Group: $finalscgName was successfully created with ID $newSCGID !
";
}
}
}
echo "
";
}
else
{
// If the user has specified a series of specific sites they want to upload to....
echo "
########## SMART COMPUTER GROUP CLONER RESULTS ############
";
foreach($verifiedResponseArray as $id=>$siteID)
{
$finalscgName = "";
// Get the site name and make sure it's modified to be XML friendly
$siteName = $siteArray[$siteID];
$siteName = htmlspecialchars($siteName, ENT_XML1);
// Modify the scg name to include the site name [Site] - scg Name to be explicit
$finalscgName = "[$siteName] - " . $smartComputerGroupName;
// Form the XML
$smartComputerGroupXML = "<computer_group><name>$finalscgName</name><id>0</id><is_smart>$computerGroupType</is_smart><site><id>$siteID</id><name>$siteName</name></site><criteria>$scgCriteria</criteria></computer_group>";
$url = $jss . "/JSSResource/computergroups/id/0";
$ch = curl_init($url);
curl_setopt( $ch, CURLOPT_SSL_VERIFYHOST, 0);
curl_setopt( $ch, CURLOPT_SSL_VERIFYPEER, 0);
curl_setopt( $ch, CURLOPT_POST, 1 );
curl_setopt( $ch, CURLOPT_HTTPHEADER, array('Content-Type: text/xml'));
curl_setopt( $ch, CURLOPT_POSTFIELDS, "$smartComputerGroupXML" );
curl_setopt( $ch, CURLOPT_USERPWD, "$apiUsername:$apiPassword");
curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 );
$result = curl_exec($ch);
curl_close($ch);
// Some error handling in case the uploads go wrong
if (strpos($result,"The server has not found anything matching the request URI"))
{
echo " FAILED: Smart Computer Group: $finalscgName was NOT created because the requested URL $url was not found!
";
}
elseif(strpos($result,"Error: Duplicate name"))
{
echo " DUPLICATE: Smart Computer Group: $finalscgName was NOT created because that Smart Computer Group name alread exists!
";
}
else
{
$xml = str_replace( '#', '', $result );
$xml = str_replace( '&', '', $xml);
$nodes = new SimpleXMLElement($xml);
$newSCGID = $nodes->id;
echo " RESULT: Smart Computer Group: $finalscgName was successfully created with ID $newSCGID !
";
}
}
echo "
";
}
}
/*
Prompt the user for which JSS they would like to modify Smart Computer Groups On.
Accepts the following variables:
- $errorResponse - If the user enters an invalid entry, the function is called again with a prompt at the top stating the error.
*/
function readJSSResponse($errorResponse)
{
global $jssURL1;
global $jssURL2;
if($errorResponse)
{
echo "Error: $errorResponse
";
}
// Display JSS choices to use.
echo "Please select the JSS you'd like to work with:
";
echo "[1] - $jssURL1
";
echo "[2] - $jssURL2
";
echo "[3] - Exit Script
";
$jssResponse = readline("JSS: ");
$jss = "";
// Set the actual URL we want to use based on user input.
switch ($jssResponse)
{
case 1:
$jss = $jssURL1;
echo "
";
break;
case 2:
$jss = $jssURL2;
echo "
";
break;
case 3:
exit;
default:
// Re-prompt if there is invalid input, and send along the error message
clear_screen();
readJSSResponse("Your entry: [$jssResponse] was invalid. I cannot let you do that Dave....");
}
// Now that we have the JSS URL, check to see if the credentials used were valid by trying to read scg data from the API.
$checkAuthResult = check_auth($jss);
// If the authenticaton was successful, move to the next step, reading which scg to clone
if($checkAuthResult == 0)
{
readSmartComputerGroupResponse($jss,$scgID);
}
elseif($checkAuthResult == 1)
{
clear_screen();
echo "#################################################################################################################################
";
echo " ERROR: Authentication Problem with JSS. API username / password was not authorized to access /JSSResource/computergroups
";
echo " Please supply new credentials within the script and try again.
";
echo " Exiting....
";
echo "#################################################################################################################################
";
exit;
}
elseif($checkAuthResult == 2)
{
clear_screen();
echo "#################################################################################################################################
";
echo " ERROR: No XML or HTML response from server. Bad Server URL? Please verify: $jss is correct...
";
echo " Please verify your JSS URL and try again.
";
echo " Exiting....
";
echo "#################################################################################################################################
";
exit;
}
else
{
clear_screen();
echo "##########################################################################################
";
echo " ERROR: An unexpected error occured. See HTML response below
$ $checkAuthResult
";
echo "##########################################################################################
";
exit;
}
}
/*
Function to ask what Smart Computer Group the user would like to clone.
Accepts the following variables:
- $jss - The JSS where the scgs reside.
- $errorResponse - If the user enters an invalid entry, the function is called again with a prompt at the top stating the error.
*/
function readSmartComputerGroupResponse($jss,$errorResponse)
{
global $apiUsername;
global $apiPassword;
global $smartComputerGroupID;
global $smartComputerGroupName;
if($errorResponse)
{
echo "Error: $errorResponse
";
}
echo "Please enter the Smart Computer Group ID (found at the end of the JSS URL) you want to clone
";
$smartComputerGroupID = readline("Smart Computer Group ID: ");
// Verify the scg is valid first
if(!empty(trim($smartComputerGroupID)) && is_numeric($smartComputerGroupID))
{
$url = $jss . "/JSSResource/computergroups/id/$smartComputerGroupID";
// Query the JSS to see if the scg exists
$json = query_jss($url);
if (strpos($json,"The server has not found anything matching the request URI"))
{
clear_screen();
readSmartComputerGroupResponse($jss,"Invalid Smart Computer Group ID: $smartComputerGroupID");
}
else
{
$xml = str_replace( '#', '', $json );
$xml = str_replace( '&', '', $xml);
// Machine Attributes we want to retrieve
$nodes = new SimpleXMLElement($xml);
$smartComputerGroupName = $nodes->name;
// If the site is valid move to the next step, which is asking the user which scgs they want to clone the SCG to
echo "
***** You have chosen to clone Smart Computer Group: $smartComputerGroupName (ID: $smartComputerGroupID)
";
$siteArray = get_sites($jss);
readSiteResponse($siteArray,$jss,"");
}
}
else
{
clear_screen();
readSmartComputerGroupResponse($jss,"Your entry: [$smartComputerGroupID] was invalid.
");
}
}
/*
Verifies user entry of Site information and prompts for what site they want to clone.
Accepts the following variables:
$siteArray - Array of all sites and their name and IDs
$jss - The URL of the JSS
$errorResponse - If the user enters an invalid entry, the function is called again with a prompt at the top stating the error.
*/
function readSiteResponse($siteArray,$jss,$errorResponse)
{
$sites = "";
$siteResponse = "";
$siteResponseArray = [];
$verifiedResponseArray = [];
$validEntry = 0;
// If there was an error, display it
if($errorResponse)
{
echo "Error: $errorResponse
";
}
echo "Please select which site(s) you would like to clone the Smart Computer Group to. For multiple sites, use a comma between each site ID #
";
// List out all Sites by ID - Name
foreach($siteArray as $siteID => $siteName)
{
echo "[$siteID] - $siteName
";
}
echo "
";
$siteResponse = readline("Site ID: ");
// The user is asked for multiple sites to use a comma, so let's break their input apart by commas
$siteResponseArray = explode(",",$siteResponse);
$verifiedResponseArray = [];
// Loop through all site entries and verify that they're valid entries.
foreach($siteResponseArray as $userSpecifiedSite)
{
if(!array_key_exists($userSpecifiedSite,$siteArray))
{
$validEntry = 1;
}
}
// If all site entries are valid, go to the next step. Else, re-prompt for input.
if($validEntry == 0)
{
foreach($siteResponseArray as $userSpecifiedSite)
{
array_push($verifiedResponseArray,$userSpecifiedSite);
}
clone_smart_computer_groups($verifiedResponseArray,$siteArray,$jss);
}
else
{
clear_screen();
readSiteResponse($siteArray,$jss,"Your Entry [$siteResponse] was invalid. An invalid site entry was made or the syntax was incorrect. Please re-enter your response.
");
}
}
// Clear the screen and display the opening dialog, and ask for what JSS the user wants to use.
clear_screen();
echo " ###################################################################
";
echo " ########## WELCOME TO THE SMART COMPUTER GROUP CLONER ############
";
echo "#################################################################
";
readJSSResponse("");
?>