Script: Best way to request user input

DanielDarnbroug
New Contributor

Hi,

Im looking to create a scripted install of an application that is hosted internally.

The script needs to download and run various commands to install the software. This we have worked out.

The application has multiple available versions and multiple locations dependent on the project you are working on.

What I need to do, is when this script executes, it asks for User interaction and asks for the server name and the agent version, this then passes into the script and off it goes installing :)

What is the best way to request the user interaction. I can make it work using automater but i'm not sure if that is the best way to go?

Thanks
Dan

22 REPLIES 22

Look
Valued Contributor III

Depends on what scripting language your using to start with, but an osascript call is a pretty good way to prompt a dialogue because you can just tell the system to present it to the user, there are a few options for text boxes and drop down lists etc...

DanielDarnbroug
New Contributor

The language is bash. But osascript is what I keep coming across. Wanted to make sure that is what other would advise before I commit to writing something.

Thank you

Hugonaut
Valued Contributor II

if going bash instead of osascript - could always use the built in JamfHelper - some examples below if not familiar

another option, nesting/calling osascript inside a shell script is a nice blend & allows for versatility all in one script.

https://github.com/retrac81/JamfHelper

https://github.com/smashism/jamfpro-scripts/blob/master/self-service/assign_this_mac.sh

________________
Looking for a Jamf Managed Service Provider? Look no further than Rocketman
________________


Virtual MacAdmins Monthly Meetup - First Friday, Every Month

mm2270
Legendary Contributor III

I'd suggest using bash but with osascript embedded into it as @Hugonaut mentioned above. I usually do this by creating a variable that will capture the input using osascript and then that variable can be passed back to the rest of the shell script and used.
If it's a standalone app this will be used in, then there's not a lot needed to make it work. If it's a script that will be run from a Jamf policy, then take a look at the second link in the post above this one for an example of how to do that, since any osascript/Applescript calls must be done as the user, not as root. The OS will block it from appearing in almost all cases if not called properly.

talkingmoose
Moderator
Moderator

This may get you started:

#!/bin/bash

results=$( /usr/bin/osascript -e "display dialog "Text and Buttons!" default answer "Some text..." buttons {"Cancel","OK"} default button {"OK"}" )

theButton=$( echo "$results" | /usr/bin/awk -F "button returned:|," '{print $2}' )
theText=$( echo "$results" | /usr/bin/awk -F "text returned:" '{print $2}' )

echo $theButton
echo $theText

exit 0

summoner2100
Contributor

Just remember to be careful using bash because the default shell when 10.15 Catalina releases this year will be ZSH. Bash is still there, but you know, for future proofing.

5Y54DMIN
Contributor

Did you every get this working?

i kinda want to do the same thing have a script that runs once, and at user login. I needs to run as root but be presented to the current user that is logged in...

how did you do it?

@DanielDarnbrough

Nix4Life
Valued Contributor

There is a trend of admins starting to use DEPNotify for something like this. One shop uses it for Adobe CC installs

Mauricio
Contributor III

@DanielDarnbrough have look at this example from another discussion in Jamf Nation.

Self Service - Get User Input with Script

We use a lot of osascript inside bash scripts here when we need to get users' input.
Furthermore as we have a global customer base we try as much as possible to localise the messages to make the process easier for all, and AppleScript (osascript) allow you to do that.

Other options:
JamfHelp is good but limited.
After that you are looking in installing another tool to support the task when osascript is native to the macOS (for now at least).

@Nix4Life We use the same concept (osascript inside bash) for Adobe CC Installs.
This allow us to offer multi-language installs with one single Adobe package/app.
All the end user has to do is choose the language for the Adobe app and after that the installation is set in that language.
Saved us a lot of policies and HD space.
Also that follows the same concept of localisation, the prompt is in the user's language.
Here is the "sanitised" example of our script to illustrate the osascript part.

#!/bin/bash

##########################################################################################
#
#   Author: Mauricio Pellizzon 2019
#
##########################################################################################

selectLangInterface() {
    consoleUser=$(stat -f %Su /dev/console)
    useHome=$(eval echo "~$consoleUser")
    userLocale=$(defaults read "$useHome"/Library/Preferences/.GlobalPreferences.plist AppleLocale)

    case "$userLocale" in
       "cs_CZ"|"da_DK"|"de_DE"|"en_AE"|"en_GB"|"en_IL"|"en_US"|"en_XM"|"es_ES"|"es_LA"|"es_MX"|"es_NA"|"fi_FI"|"fr_CA"|"fr_FR"|"fr_MA"|"fr_XM"|"hu_HU"|"it_IT"|"ja_JP"|"ko_KR"|"nb_NO"|"nl_NL"|"pl_PL"|"pt_BR"|"ru_RU"|"sv_SE"|"tr_TR"|"uk_UA"|"zh_CN"|"zh_TW");;
       * ) userLocale=${userLocale::${#userLocale}-3};;
    esac

    case "$userLocale" in

        "cs_CZ" ) selectLang="Zvolte jazyk" lang="Jazyk" ;;
        "cs" ) selectLang="Zvolte jazyk" lang="Jazyk" ;;
        "da_DK" ) selectLang="Vælg sprog" lang="Sprog" ;;
        "da" ) selectLang="Vælg sprog" lang="Sprog" ;;
        "de_DE" ) selectLang="Sprache wählen" lang="Sprache" ;;
        "de" ) selectLang="Sprache wählen" lang="Sprache" ;;
        "en_GB" ) selectLang="Select Language" lang="Language" ;;
        "en_US" ) selectLang="Select Language" lang="Language" ;;
        "en_XM" ) selectLang="Select Language" lang="Language" ;;
        "en" ) selectLang="Select Language" lang="Language" ;;
        "es_ES" ) selectLang="Seleccione idioma" lang="Idioma" ;;
        "es_LA" ) selectLang="Seleccione idioma" lang="Idioma" ;;
        "es_MX" ) selectLang="Seleccione idioma" lang="Idioma" ;;
        "es_NA" ) selectLang="Seleccione idioma" lang="Idioma" ;;
        "es" ) selectLang="Seleccione idioma" lang="Idioma" ;;
        "fi_FI" ) selectLang="Valitse kieli" lang="Kieli" ;;
        "fi" ) selectLang="Valitse kieli" lang="Kieli" ;;
        "fr_CA" ) selectLang="Choisir une langue" lang="Langue" ;;
        "fr_FR" ) selectLang="Choisir une langue" lang="Langue" ;;
        "fr_XM" ) selectLang="Choisir une langue" lang="Langue" ;;
        "fr" ) selectLang="Choisir une langue" lang="Langue" ;;
        "hu_HU" ) selectLang="Nyelv választása" lang="Nyelv" ;;
        "hu" ) selectLang="Nyelv választása" lang="Nyelv" ;;
        "it_IT" ) selectLang="Selezionare la lingua" lang="Lingua" ;;
        "it" ) selectLang="Selezionare la lingua" lang="Lingua" ;;
        "ja_JP" ) selectLang="言語を選択" lang="言語" ;;
        "ja" ) selectLang="言語を選択" lang="言語" ;;
        "ko_KR" ) selectLang="언어 선택" lang="언어" ;;
        "ko" ) selectLang="언어 선택" lang="언어" ;;
        "nb_NO" ) selectLang="Velg språk" lang="Språk" ;;
        "nb" ) selectLang="Velg språk" lang="Språk" ;;
        "nl_NL" ) selectLang="Taal selecteren" lang="Taal" ;;
        "nl" ) selectLang="Taal selecteren" lang="Taal" ;;
        "pl_PL" ) selectLang="Wybierz język" lang="Język" ;;
        "pl" ) selectLang="Wybierz język" lang="Język" ;;
        "pt_BR" ) selectLang="Selecione o idioma" lang="Idioma" ;;
        "pt" ) selectLang="Selecione o idioma" lang="Idioma" ;;
        "ru_RU" ) selectLang="Выбрать язык" lang="Язык" ;;
        "ru" ) selectLang="Выбрать язык" lang="Язык" ;;
        "sv_SE" ) selectLang="Välj språk" lang="Språk" ;;
        "sv" ) selectLang="Välj språk" lang="Språk" ;;
        "tr_TR" ) selectLang="Dil Seçin" lang="Dil" ;;
        "tr" ) selectLang="Dil Seçin" lang="Dil" ;;
        "uk_UA" ) selectLang="Вибрати мову" lang="Мова" ;;
        "uk" ) selectLang="Вибрати мову" lang="Мова" ;;
        "zh_CN" ) selectLang="选择语言" lang="语言" ;;
        "zh_TW" ) selectLang="選取語言" lang="語言" ;;
    esac
}

localizableString() {
lang="$1"
selectLang="$2"

choosenLanguage=$(sudo -u "$userName" osascript <<EOF
set systemUIServer to "SystemUIServer"
if systemUIServer is not in every paragraph of (do shell script "ps -Ac -o comm=") then return
use AppleScript version "2.4" -- Yosemite (10.10) or later
use scripting additions
tell application systemUIServer
set myOK to localized string of "OK" from table "Localizable" in bundle "/System/Library/CoreServices/Installer.app"
set myCancel to localized string of "Cancel" from table "Localizable" in bundle "/System/Library/CoreServices/Installer.app"
tell application "System Events"
    activate
    set theLanguages to {"Česky", "Dansk", "Deutsch", "English يدعم العربية", "English (International)", "English תומך עברית", "English (North America)", "Español", "Español (América Latina)", "Suomi", "Français canadien", "Français", "Français (Maroc)", "Magyar", "Italiano", "日本語", "한국어", "Norsk (Bokmål)", "Nederlands", "Polski", "Português do Brasil", "Pусский", "Svenska", "Türkçe", "Українська", "У简体中文", "繁體中文"}
    set theChoosenLanguage to choose from list theLanguages with title "Adobe Creative Cloud App $lang" with prompt "$selectLang" OK button name myOK cancel button name myCancel
end tell
end tell
EOF
)
}

localeSetup() {
# user interface to setup the locale....
selectLangInterface
localizableString "$lang" "$selectLang"

case "$choosenLanguage" in
    "Česky" ) choosenLocale="cs_CZ";;
    "Dansk" ) choosenLocale="da_DK";;
    "Deutsch" ) choosenLocale="de_DE";;
    "English يدعم العربية" ) choosenLocale="en_AE";;
    "English (International)" ) choosenLocale="en_GB";;
    "English תומך עברית" ) choosenLocale="en_IL";;
    "English (North America)" ) choosenLocale="en_US";;
    "Español" ) choosenLocale="es_ES";;
    "Español (América Latina)" ) choosenLocale="es_MX";;
    "Suomi" ) choosenLocale="fi_FI";;
    "Français canadien" ) choosenLocale="fr_CA";;
    "Français" ) choosenLocale="fr_FR";;
    "Français (Maroc)" ) choosenLocale="fr_MA";;
    "Magyar" ) choosenLocale="hu_HU";;
    "Italiano" ) choosenLocale="it_IT";;
    "日本語" ) choosenLocale="ja_JP";;
    "한국어" ) choosenLocale="ko_KR";;
    "Norsk (Bokmål)" ) choosenLocale="nb_NO";;
    "Nederlands" ) choosenLocale="nl_NL";;
    "Polski" ) choosenLocale="pl_PL";;
    "Português do Brasil" ) choosenLocale="pt_BR";;
    "Pусский" ) choosenLocale="ru_RU";;
    "Svenska" ) choosenLocale="sv_SE";;
    "Türkçe" ) choosenLocale="tr_TR";;
    "Українська" ) choosenLocale="uk_UA";;
    "У简体中文" ) choosenLocale="zh_CN";;
    "繁體中文" ) choosenLocale="zh_TW";;
    * ) echo "$FUNCNAME" ;;
esac
}

##########################################################################################
#
# SCRIPT CONTENTS
#
##########################################################################################

userName=$(ls -la /dev/console | cut -d " " -f 4)
localeSetup
echo "$choosenLanguage"

Attached the screen shot of the policy for illustration.

3704082ec5af4777b446552ecd69b3b6

PS: We write a local file with the chosen language for subsequent installs, so this prompt is done once unless the user requires another language, which is available via another self-service policy.

4edc855361b240cea7caf233130e14dc

joethedsa
Contributor II

@mm2270, I'm curious what the syntax would be to get the user's input via a dialog that they enter and then set it as a variable. I have the following osascript to enter a computer name. What ever value is entered, it will use that to rename the computer.

#!/bin/bash

/usr/bin/osascript << EOF 
property compName : ""

repeat while compName is ""
    tell application "Finder"
        activate
        display dialog "Set the Computer Name. (Example: iMac-12345, mbp-67890)" default answer compName
        set compName to text returned of result
    end tell
end repeat

try
    do shell script "hostname " & quoted form of compName 
on error errorMsg number errorNum
    display alert "Error " & errorNum message errorMsg buttons "Cancel" default button 1
end try
EOF

name=$(hostname)
scutil --set ComputerName "${name}"
scutil --set LocalHostName "${name}"

If I want to take it one step further where the user would enter the text but then append the serial number of the Mac to it without them having to enter it, do you know what that would look like?

For example, the end result would be something like "mbp-serialnumber". "mbp-" would be what the user would enter and "serialnumber" would be put in automagically.

It would be more ideal but probably requires more scripting smarts to use if/then or case statements to name it automatically. For example, use the below command to get the model number

(system_profiler SPHardwareDataType | grep "Model Name" | awk -F ":" '{ sub(" ",""); print $2}' | tr -d ' ')

then use the result to name it. For example, if the model is an iMac, use the string "im-" or if the model is MacBook, use the string "mb-", etc then append the serial number. This would minimize "user typos" and offer consistent naming conventions.

mm2270
Legendary Contributor III

@joethedsa Hi there. So generally with these kinds of situations, where input from the user is an absolute requirement before proceeding with another operation, I usually handle this more in the bash section than within the AppleScript/osascript part. I put the portion where AppleScript requests input into a bash function. Outside of that, I check the variable that was returned, and if it was empty or not what we expected, I re-run the function, so it pops up the AppleScript window to request input again. Essentially it creates a loop that won't exit until the input is something we expect. That could be as simple as "not empty", but you could go further with it and check the length of the string returned for example, so if it's not at least "x" number of characters long, re-pop up the window for input.

Once we have something that works, then you can go on with combining the serial number and the user input into a single string and then use that to rename the Mac.

Here's what my script might look like.

#!/bin/bash

function askForCompName ()
{

## Capture the user input into a variable
COMPNAME=$(/usr/bin/osascript << EOF 
tell application "System Events"
    activate
    display dialog "Set the Computer Name. (Example: iMac-12345, mbp-67890)" default answer ""
    set compName to text returned of result
end tell
EOF)

## Check the variable to make sure it's not empty...
if [ "$COMPNAME" == "" ]; then
    echo "Computer name was not entered. Re prompting the user..."
    askForCompName
else
    echo "Computer name entered was: $COMPNAME"
fi

}

## Run the function above
askForCompName

## Get the Macs serial number
SERIALNUMBER=$(/usr/sbin/ioreg -rd1 -c IOPlatformExpertDevice | awk -F'"' '/IOPlatformSerialNumber/{print $4}')

## Generate a new computer name from the above information
NEWCOMPNAME="${COMPNAME}-${SERIALNUMBER}"

## Rename the Mac using scutil (can also use the Jamf binary here)
/usr/sbin/scutil --set ComputerName "${NEWCOMPNAME}"
/usr/sbin/scutil --set LocalHostName "${NEWCOMPNAME}"

I haven't actually tested this script in it's entirety. I only tested the main function loop where it keeps popping up unless some input is given, which does work. The rest should also work since it's pretty simple in what its doing.

joethedsa
Contributor II

@mm2270, Thank you! I tested this out and it works just as intended. I like the error checking loop if there is no input. I did noticed though, if the Cancel button is clicked, it also just loops. Is that intended? I ran it in Catalina.

Is there a way to make a "cap" as to only allowing a maximum of two characters for input? These systems will be bound to Active Directory. There is a 15 character limit that it uses. Adding two letters, a dash, and then the serial number reaches 15 characters.

shaquir
Contributor III

Hi @joethedsa ,
I haven't tested this, but you should be able to accomplish your cap by using Regex to test against the input:

#!/bin/bash
#Script to prompt user for two letter Asset Tag and set Computer name to Asset Tag and Serial Number
#Shaquir Tannis
#AppleScript for user input 
promptUser ()
{
assetTag=$(/usr/bin/osascript << EOF
tell application "System Events"
    activate
    set input to display dialog "Enter Asset Tag: " default answer "##" buttons {"OK"} default button 1
    return text returned of input as string
end tell
EOF
)

#Loop until user inputs two letters
until [[ "$assetTag" =~ ^[a-zA-Z]{2}$ ]]
do
promptUser
done
}

#Call on function to prompt user
promptUser

#Grab Computer Serial Number
serialNumber=$(system_profiler SPHardwareDataType | awk '/Serial/ {print $4}')

#Set computer name
/usr/sbin/scutil --set HostName "$assetTag-$serialNumber"
/usr/sbin/scutil --set LocalHostName "$assetTag-$serialNumber"
/usr/sbin/scutil --set ComputerName "$assetTag-$serialNumber"
/usr/local/bin/jamf setComputerName -name "$assetTag-$serialNumber"

exit
fi

joethedsa
Contributor II

@shaquir, thanks for your input. I added the below syntax from your script but adjusted the function name and the variable to fit the one @mm2270 shared and it worked as expected:

until [[ "$assetTag" =~ ^[a-zA-Z]{2}$ ]]
do
promptUser
done

For some reason though if I hit Cancel, the dialog box just re-appears. Still trying to work through that annoyance.

shaquir
Contributor III

I'm sure there is a better way to do this, but you could try:

promptUser ()
{
assetTag=$(/usr/bin/osascript << EOF
tell application "System Events"
    activate
    set input to display dialog "Enter Asset Tag: " default answer "##" buttons {"Not Now", "OK"} default button 2
    set buttonSelected to button returned of the result
    if buttonSelected is "OK" then
        return text returned of input as string
    else
        return buttonSelected 
    end if
end tell
EOF
)   

if [ "$assetTag" = "Not Now" ];
then
    echo "User Selected Not Now"
    exit 1
else
    #Loop until user inputs two letters
    until [[ "$assetTag" =~ ^[a-zA-Z]{2}$ ]]
    do
    promptUser
    done
fi
}

#Call on function to prompt user
promptUser

#Grab Computer Serial Number
serialNumber=$(system_profiler SPHardwareDataType | awk '/Serial/ {print $4}')

#Set computer name
/usr/sbin/scutil --set HostName "$assetTag-$serialNumber"
/usr/sbin/scutil --set LocalHostName "$assetTag-$serialNumber"
/usr/sbin/scutil --set ComputerName "$assetTag-$serialNumber"
/usr/local/bin/jamf setComputerName -name "$assetTag-$serialNumber"
exit
fi

I substituted Applescript's default "Cancel" to "Not Now". There is probably a better way to handle the error, but this should work for you.

oli
New Contributor III

If I run the script, I was asked to allow Jamf to control System Events.
Is there a way to configure this using a configuration policy or something else?
0c2d300739e8425c902b207b0fff074c

shaquir
Contributor III

Hi @oli,
You'd need to whitelist Jamf using the AppleEvents: Creating Privacy Preferences Policy Control profiles for macOS.
Untested but this may be a good start for you: Jamf_All_Notifications_v1.mobileconfig

oli
New Contributor III

Hi @shaquir ,
thank you for quick responce! I will give it a try.
Best regards!

AVmcclint
Honored Contributor

I have built a script that prompts for the asset tag of the computer and then applies it to the Computer record in Jamf. I used snippets of code from a couple posts here on JamfNation and some extra bits on the web. It functions by displaying a dialog box with Cancel and OK buttons and a text box for entering the asset tag. If the user types nothing and hits OK, it will prompt again. If the user hits Cancel, it exits without doing anything. If the user types anything in the box and hits OK, that information is applied to the Computer record. One Caveat is if the user enters spaces in the text, only the text before the first space will be applied. 

 

#!/bin/sh

# the purpose of this script is to prompt the user for the asset tag of the computer, save it as a variable,
# then use that variable in jamf recon -assetTag $ASSETTAG so the computer record gets updated.


function askForAssetTag ()
{

## Capture the user input into a variable
ASSETTAG=$(/usr/bin/osascript << EOF 
tell application "System Events"
    activate
    display dialog "Please enter the Company Asset Tag number of this computer. (please include all the leading zeros. No spaces permitted)" default answer ""
	set assetTag to text returned of result

end tell

EOF)
	
# Check status of osascript. Clicking Cancel will just exit without doing anything.
if [ "$?" != "0" ] ; then
   echo "User aborted. Exiting..."
   exit 0
fi
	
## Check the variable to make sure it's not empty...
if [ "$ASSETTAG" == "" ]; then
    echo "Asset Tag was not entered. Re prompting the user..."
    askForAssetTag
else
    echo "Asset Tag entered was: $ASSETTAG"
fi

}

## Run the function above
askForAssetTag

#then apply the output to the recon -assetTag command
jamf recon -assetTag $ASSETTAG

echo "The asset tag of this computer is now $ASSETTAG"
exit 0

 

 I'm sure you could apply RegEx to make sure the text entered meets your organization's standards, but that's beyond my skills. My intent with the script where I am is that only the tech setting up the computer would need to run this and it is in their best interest to enter valid text.

Mauricio
Contributor III

@AVmcclint 

Not sure where that caveat is coming from.

Is that in Jamf record? Have you tried to change this part of your code?

jamf recon -assetTag "$ASSETTAG"

AVmcclint
Honored Contributor

the Jamf recon -assetTag xxxxx  command will only accept the contiguous text entered after -assetTag .  It will accept all of "2374657896" but it will only accept the first part of "23746 57896" as "23746" I discovered that in my testing. I did not try putting the variable in quotes though because our asset tags have to be entered as a single chunk of digits with no spaces in all our systems anyway.

Mauricio
Contributor III

Ok that makes sense, if spaces are not allowed just clean up before putting in Jamf 

 

ASSETTAG=$(echo "$ASSETTAG" | sed 's/ //g')