Smart Computer Group Cloner

seansb
New Contributor III

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("");      
?>
0 REPLIES 0