JamfMigrator icon indicating copy to clipboard operation
JamfMigrator copied to clipboard

Feature Request - App Installers Support

Open pwadenz opened this issue 1 year ago • 5 comments

We have started using Jamf App Installers in more tenants to aid with ongoing patch management. It would be great to be able to copy the settings for App Installers and the Apps themselves to save us time when we create new tenants.

pwadenz avatar May 09 '24 19:05 pwadenz

Waiting for a published API endpoint to work with this.

BIG-RAT avatar Feb 17 '25 21:02 BIG-RAT

Thanks. Fingers crossed - if anyone else sees this, I created a FR in Jamf Ideas for this last year which you could upvote - https://ideas.jamf.com/ideas/PCAP-I-144

pwadenz avatar Feb 18 '25 00:02 pwadenz

Was about to make the same feature request. 😊 Upvoted PCAP-I-144.

JordyThery avatar Jun 27 '25 03:06 JordyThery

For what it's worth I've taken a look at https://github.com/tyler-tee/JNUC-2023 and wrote this very small script to list existing app installers, maybe its something you can work with? I'm terrible with scripts so don't think I'll be much more of assistance:

#!/usr/bin/env bash
set -e

# === Jamf Pro Settings ===
jamf_url="JAMFPROURL"
jamf_user="JAMFPROUSERNAME"
jamf_pass="JAMFPROPASSWORD"

# === Get Bearer Token ===
echo "Requesting bearer token..."
auth_response=$(curl -s -u "${jamf_user}:${jamf_pass}" \
  -X POST "${jamf_url}/api/v1/auth/token")

bearer_token=$(echo "$auth_response" | jq -r .token)

if [[ -z "$bearer_token" || "$bearer_token" == "null" ]]; then
  echo "Failed to get bearer token"
  echo "$auth_response"
  exit 1
fi
echo "Got token"

# === Get all App Installer deployments ===
echo "Fetching App Installer deployments..."
response=$(curl -s -w "\nHTTP_STATUS:%{http_code}\n" \
  -X GET "${jamf_url}/api/v1/app-installers/deployments" \
  -H "Authorization: Bearer ${bearer_token}" \
  -H "Accept: application/json")

http_status=$(echo "$response" | tr -d '\r' | awk -F'HTTP_STATUS:' '{print $2}')

if [[ "$http_status" -eq 200 ]]; then
  echo "App Installer deployments:"
  echo "$response" | sed '/HTTP_STATUS:/d' | jq .
else
  echo "Failed to fetch deployments (HTTP $http_status). Response:"
  echo "$response" | sed '/HTTP_STATUS:/d'
  exit 1
fi

I also have this small example to create the app installers (in this example hardcoded for Word, Powerpoint and Excel):

#!/usr/bin/env bash
set -e

# === Target Jamf Pro Tenant Settings ===
jamf_url="JAMFPROURL"
jamf_user="JAMFPROUSERNAME"
jamf_pass="JAMFPROPASSWORD"

# === Get Bearer Token ===
echo "Requesting bearer token..."
auth_response=$(curl -s -u "${jamf_user}:${jamf_pass}" \
  -X POST "${jamf_url}/api/v1/auth/token")

bearer_token=$(echo "$auth_response" | jq -r .token)

if [[ -z "$bearer_token" || "$bearer_token" == "null" ]]; then
  echo "Failed to get bearer token"
  echo "$auth_response"
  exit 1
fi
echo "Got token"

# === Function to create an App Installer ===
create_installer () {
  local name="$1"
  local appTitleId="$2"
  local categoryId="$3"
  local smartGroupId="$4"
  local siteId="$5"
  local deploymentType="$6"
  local updateBehavior="$7"
  local enabled="$8"

  echo "Creating installer: $name"

  payload=$(jq -n \
    --arg name "$name" \
    --arg appTitleId "$appTitleId" \
    --arg categoryId "$categoryId" \
    --arg smartGroupId "$smartGroupId" \
    --arg siteId "$siteId" \
    --arg deploymentType "$deploymentType" \
    --arg updateBehavior "$updateBehavior" \
    --argjson enabled "$enabled" \
    '{
      name: $name,
      enabled: $enabled,
      appTitleId: $appTitleId,
      siteId: $siteId,
      categoryId: $categoryId,
      smartGroupId: $smartGroupId,
      deploymentType: $deploymentType,
      updateBehavior: $updateBehavior,
      installPredefinedConfigProfiles: true
    }'
  )

  response=$(curl -s -w "\nHTTP_STATUS:%{http_code}\n" \
    -X POST "${jamf_url}/api/v1/app-installers/deployments" \
    -H "Authorization: Bearer ${bearer_token}" \
    -H "Content-Type: application/json" \
    -d "$payload")

  http_status=$(echo "$response" | tr -d '\r' | awk -F'HTTP_STATUS:' '{print $2}')

  if [[ "$http_status" -eq 200 || "$http_status" -eq 201 ]]; then
    echo "Created $name successfully"
  else
    echo "Failed to create $name (HTTP $http_status)"
    echo "$response" | sed '/HTTP_STATUS:/d'
  fi
}

# === Create the three installers ===
create_installer "Microsoft Word 365"       "0A6" "3" "1" "-1" "SELF_SERVICE" "AUTOMATIC" true
create_installer "Microsoft PowerPoint 365" "0A3" "3" "1" "-1" "SELF_SERVICE" "AUTOMATIC" true
create_installer "Microsoft Excel 365"      "09C" "3" "1" "-1" "SELF_SERVICE" "AUTOMATIC" true

This all seems to work in our environment.

I asked our friend ChatGPT to create a migration script based on the code above (but I haven't tested that one yet!)

#!/usr/bin/env bash
set -e

# === Source and Destination Jamf Pro Settings ===
SRC_JAMF_URL="https://source-tenant.jamfcloud.com"
SRC_JAMF_USER="source_user"
SRC_JAMF_PASS="source_password"

DEST_JAMF_URL="https://destination-tenant.jamfcloud.com"
DEST_JAMF_USER="dest_user"
DEST_JAMF_PASS="dest_password"

# === Function to get bearer token ===
get_token() {
  local url="$1"
  local user="$2"
  local pass="$3"
  token=$(curl -s -u "${user}:${pass}" \
    -X POST "${url}/api/v1/auth/token" | jq -r .token)
  if [[ -z "$token" || "$token" == "null" ]]; then
    echo "Failed to get bearer token for $url"
    exit 1
  fi
  echo "$token"
}

# === Get tokens ===
echo "Requesting token for source tenant..."
SRC_TOKEN=$(get_token "$SRC_JAMF_URL" "$SRC_JAMF_USER" "$SRC_JAMF_PASS")
echo "Got source token"

echo "Requesting token for destination tenant..."
DEST_TOKEN=$(get_token "$DEST_JAMF_URL" "$DEST_JAMF_USER" "$DEST_JAMF_PASS")
echo "Got destination token"

# === Fetch App Installers from source tenant ===
echo "Fetching App Installers from source tenant..."
SRC_RESPONSE=$(curl -s -H "Authorization: Bearer ${SRC_TOKEN}" \
  -H "Accept: application/json" \
  "${SRC_JAMF_URL}/api/v1/app-installers/deployments")

DEPLOYMENTS=$(echo "$SRC_RESPONSE" | jq -c '.results[]')

# === Loop through each deployment and create in destination tenant ===
echo "Migrating deployments to destination tenant..."
for d in $DEPLOYMENTS; do
  name=$(echo "$d" | jq -r .name)
  appTitleId=$(echo "$d" | jq -r .app.id)
  categoryId=$(echo "$d" | jq -r .category.id)
  smartGroupId=$(echo "$d" | jq -r .smartGroup.id)
  siteId=$(echo "$d" | jq -r .site.id)
  deploymentType=$(echo "$d" | jq -r .deploymentType)
  updateBehavior=$(echo "$d" | jq -r .updateBehavior)
  enabled=$(echo "$d" | jq -r .enabled)
  selfServiceEnabled=$(echo "$d" | jq -r '.selfServiceSettings.enabled // true')
  selfServiceCategoryId=$(echo "$d" | jq -r '.selfServiceSettings.categoryId // .category.id')
  selfServicePriority=$(echo "$d" | jq -r '.selfServiceSettings.priority // "DEFAULT"')

  echo "Creating installer: $name"

  payload=$(jq -n \
    --arg name "$name" \
    --arg appTitleId "$appTitleId" \
    --arg categoryId "$categoryId" \
    --arg smartGroupId "$smartGroupId" \
    --arg siteId "$siteId" \
    --arg deploymentType "$deploymentType" \
    --arg updateBehavior "$updateBehavior" \
    --argjson enabled "$enabled" \
    --arg selfServiceCategoryId "$selfServiceCategoryId" \
    --arg selfServicePriority "$selfServicePriority" \
    '{
      name: $name,
      enabled: $enabled,
      appTitleId: $appTitleId,
      siteId: $siteId,
      categoryId: $categoryId,
      smartGroupId: $smartGroupId,
      deploymentType: $deploymentType,
      updateBehavior: $updateBehavior,
      installPredefinedConfigProfiles: true,
      selfServiceSettings: {
        enabled: true,
        categoryId: $selfServiceCategoryId,
        priority: $selfServicePriority
      }
    }'
  )

  RESPONSE=$(curl -s -w "\nHTTP_STATUS:%{http_code}\n" \
    -X POST "${DEST_JAMF_URL}/api/v1/app-installers/deployments" \
    -H "Authorization: Bearer ${DEST_TOKEN}" \
    -H "Content-Type: application/json" \
    -d "$payload")

  HTTP_STATUS=$(echo "$RESPONSE" | tr -d '\r' | awk -F'HTTP_STATUS:' '{print $2}')

  if [[ "$HTTP_STATUS" -eq 200 || "$HTTP_STATUS" -eq 201 ]]; then
    echo "Created $name successfully"
  else
    echo "Failed to create $name (HTTP $HTTP_STATUS)"
    echo "$RESPONSE" | sed '/HTTP_STATUS:/d'
  fi
done

JordyThery avatar Aug 28 '25 06:08 JordyThery

Nice! I've been waiting for those API endpoints to become part of the published Jamf Pro (https:///api/v.....) API. Hate to get something done only to have the endpoint change (without warning) shortly afterwards. However, there may come a time where I tire of waiting, enough people have chimed in, and I have some spare time...

BIG-RAT avatar Aug 28 '25 10:08 BIG-RAT