Skip to main content
Solved

Progress bar of cached package using CocoaDialog


Forum|alt.badge.img+14
  • Valued Contributor
  • 296 replies

I've been trying to figure it out but is there a way to show a progress bar with precentage (most likely using CocoaDialog) to show the download progress of a cached package?

We are kicking off a cached package policy via self service and our users have requested more feedback on the progress of the cached package download. I imagine this can be done through CocoaDialog but can't place it. Any help would be appreciated.

Best answer by iJake

CocoaDialog can be finicky and you have to use the beta of version 3 for progress to work but here is code to show progress of caching a package. I have a function for when I call the installer too if you need that. You have to tell it the size of the package since it can't be determined dynamically with the way packages ae cached currently.

cdPath="/Path/To/cocoaDialog.app/Contents/MacOS/cocoaDialog"
cdIcon="/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/ApplicationsFolderIcon.icns"
cdTitle="Dialog Title"
pkgTotal=12064712 #Must know the size of the package
pkgPartial=0
downloadFolder="/Library/Application Support/JAMF/Downloads"
waitingRoomFolder="/Library/Application Support/JAMF/Waiting Room"
dmgName="Package.dmg"

## CocoaDialog Function
dialogDownload()
{
## Create the name pipe input for the progressbar & set brief pause
rm -f /tmp/hpipe
mkfifo /tmp/hpipe
sleep 0.2

## Display progress bar with initial text settings
"$cdPath" progressbar --stoppable --title "Upgrade Package Download Progress" --text "Downloading..." --float < /tmp/hpipe > /tmp/_out &

exec 3< <(yes)

## Send progress through the named pipe
exec 3<> /tmp/hpipe

if { >&3; } 2> /dev/null
    then
        writeLog "Pipe RW verified"
    else
        writeLog "Pipe RW failed"
fi

#Determine PID of jamf process so it can be killed.
trap : SIGTERM SIGINT

echo $$

(jamf policy -event PayloadName ) > /tmp/_out2 &

jamfPID=$!

## Give the policy time to start
sleep 5

#Initialize error counter
counter=0
#While loop to generate package download percent
while [[ $pkgPartial != $pkgTotal ]]
    do
        #Watches for the output of CocoaDialog to equal stopped if "Stop" button is clicked. Cancels install.
        dialogStopCheck=`cat /tmp/_out`
        if [[ "$dialogStopCheck" = "stopped" ]]
            then
                kill $jamfPID
                rm -f /tmp/_out
                rm -f /tmp/_out2
                writeLog "Upgrade cancelled by user."
                exit
        fi
        #Watches for possible jamf binary being upgraded which will fail download of package.
        binaryUpgradeCheck=`cat /private/tmp/_out2 | grep "Upgrading jamf binary..."`
        if [[ "$binaryUpgradeCheck" != "" ]]
            then
                kill $jamfPID
                rm -f /tmp/_out
                rm -f /tmp/_out2
                writeLog "jamf binary upgrade detected. Fixing..."
                jamf policy -event PayloadName >> "$logFile"
                sleep 20
                dialogDownload
        fi
        #Error counter check. Die at 10 errors.
        if [[ $counter -gt 9 ]]
            then
                echo "Package Download failed..." >&3
                sleep 5
                kill $jamfPID
                writeLog "Package download failed."
                exit
            else
                #If the package exists in the download folder determine percentage downloaded.
                if [[ -f  "$downloadFolder"/"$dmgName" ]]
                    then
                        pkgPartial=`du "$downloadFolder"/"$dmgName" | awk '{print $1}'`
                        modifiedTime=`stat -s "$downloadFolder"/"$dmgName" | awk '{print $10}' | sed 's/.*=//'`
                        currentTime=`date +%s`
                        timeDifference=`expr $currentTime - $modifiedTime`
                        #If the downloading file has not been updated in the 10 seconds relative to the current time download may have stalled. Increment error counter.
                        if [[ $timeDifference -gt 10 ]]
                            then
                                echo "$xferPercentage Download may have stalled... ${xferPercentage}%" >&3
                                sleep 5 
                                counter=$(expr $counter + 1)
                            else
                                #Calculate percentage
                                if [[ $pkgPartial =~ ^-?[0-9]+$ ]]
                                    then
                                        xferPercentage=`echo "scale=0; $pkgPartial*100/$pkgTotal" | bc`
                                        echo "$xferPercentage Downloading... ${xferPercentage}%" >&3
                                fi
                        fi
                    #Determine if the file has been moved to the JAMF Waiting room. Exit loop if so to begin install.
                    elif [[ -f "$waitingRoomFolder"/"$dmgName" ]]
                        then
                            writeLog "$dmgName downloaded to $waitingRoomFolder"
                            break
                    else
                        counter=$(expr $counter + 1)
                fi
        fi      
done

## Turn off progress bar by closing file descriptor 3 and removing the named pipe
echo "Closing progress bar."
exec 3>&-
rm -f /tmp/hpipe
}
View original
Did this topic help you find an answer to your question?

35 replies

Forum|alt.badge.img+17
  • Contributor
  • 881 replies
  • January 22, 2016

If you had a way of determining the progress in the script, you could update a CocoaDialog progress bar every second or so to reflect it. I suppose that could be done by monitoring the size of the file in the waiting room and comparing it to the total file size, which you'd probably need to manually determine and pass to the script somehow.


mm2270
Forum|alt.badge.img+16
  • Legendary Contributor
  • 7880 replies
  • January 22, 2016

Are you using a standard caching policy in your JSS policy, like using the "Cache" option for the package? Or, are you using something like an installer that downloads and then installs the "cached" package into a special directory for use later?

If you're using the built in policy option, I don't think there's a way to do it, since the standard policy stuff doesn't have any progress output I'm aware of you can send back to cocoaDialog to "move" the progress bar.

One possible way, which I use a bit in one of my scripts, but haven't used against JSS hosted pkgs, is to do a curl command on the package on your http Casper Share and get the size of the file, which shows up under a line called "Length" You can then use that info in a loop process that runs a du command on the file being downloaded and does math to convert to a percentage. In other words, use the total size and compare against the current size of the file being downloaded to figure out how much percentage-wise its complete. That can then be piped back to cocoaDialog to use for the percentage of the progressbar.
Does that make any sense?

Edit: @alexjdale basically said the same thing I just did.


Forum|alt.badge.img+14
  • Author
  • Valued Contributor
  • 296 replies
  • January 22, 2016

@mm2270 it does make sense and yes @alexjdale essentially said the same thing, ha. Thank you both for responding.

We are just using the cache option/policy from the JSS and then a separate policy to install it. Instead of using curl, you think I could hard code the size of the package since it's static?

Also, would the script with the cocoadialog commands, order wise, have to kick off after the cache policy started? So basically, in the policy, it would go "cache xyz.pkg" then run "progressbar.sh"? I'm just wondering if the script will kick off even if the package hasn't finished caching yet.


mm2270
Forum|alt.badge.img+16
  • Legendary Contributor
  • 7880 replies
  • January 22, 2016

Yes, you could hardcode a value of the actual full file size into the script to use (or pass it as a parameter) instead of getting it dynamically with curl.

As for the order, what would need to happen is the caching policy would need to be kicked off, maybe with something like a jamf policy -trigger <sometrigger> call, but placed into the background so the script can continue and then begin checking the local file size. This may all be kind of tricky, because it can take several moments for the download to even start, so essentially, you'd need to include some kind of wait process, where the script waits to see the initial file created in the /Library/Application Support/JAMF/Waiting Room/ directory before it begins the progress bar perhaps, or, pop up the progress bar, but with the understanding that "progress" on the download may not start for a short period.

If I have the time I can try putting together a sample script you can use. I think this is possible, but since I can't say I've done exactly this, I'd have to play with it to see how it would work in practice.

Hope that helps a little for the moment.


Forum|alt.badge.img+14
  • Author
  • Valued Contributor
  • 296 replies
  • January 22, 2016

@mm2270 Yea I think the tricky part is getting the cache to somehow delay so the script can run. We are using a custom trigger for the caching policy to start.

Although now that I write this, I realize I could place it in the script that calls the cache policy to start. I'm just not sure how to put the custom trigger policy on a delay so the script can continue to run to get the percentage once the file starts to cache using cocoadialog.

Thanks for any help you can provide and I may reach out to my rep to see if there is a way to delay a custom trigger policy and then make it resume again, all in the same script. If/when I come up with anything in terms of workflow, I'll post it back here.


Forum|alt.badge.img+17
  • Contributor
  • 881 replies
  • January 22, 2016

In scripts executed by Casper, I've found that you have to use a launchdaemon to truly spin off another execution thread.

You could look at my code here to get an idea of how to echo out and run a launchdaemon/script: https://jamfnation.jamfsoftware.com/discussion.html?id=14940

You should be able to create and load a launchdaemon that runs the policy trigger to cache the package. The main script could then monitor the download and provide progress bar updates. I also have a progress bar update loop in there that you might find useful with some modification.


iJake
Forum|alt.badge.img+21
  • Contributor
  • 279 replies
  • Answer
  • January 22, 2016

CocoaDialog can be finicky and you have to use the beta of version 3 for progress to work but here is code to show progress of caching a package. I have a function for when I call the installer too if you need that. You have to tell it the size of the package since it can't be determined dynamically with the way packages ae cached currently.

cdPath="/Path/To/cocoaDialog.app/Contents/MacOS/cocoaDialog"
cdIcon="/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/ApplicationsFolderIcon.icns"
cdTitle="Dialog Title"
pkgTotal=12064712 #Must know the size of the package
pkgPartial=0
downloadFolder="/Library/Application Support/JAMF/Downloads"
waitingRoomFolder="/Library/Application Support/JAMF/Waiting Room"
dmgName="Package.dmg"

## CocoaDialog Function
dialogDownload()
{
## Create the name pipe input for the progressbar & set brief pause
rm -f /tmp/hpipe
mkfifo /tmp/hpipe
sleep 0.2

## Display progress bar with initial text settings
"$cdPath" progressbar --stoppable --title "Upgrade Package Download Progress" --text "Downloading..." --float < /tmp/hpipe > /tmp/_out &

exec 3< <(yes)

## Send progress through the named pipe
exec 3<> /tmp/hpipe

if { >&3; } 2> /dev/null
    then
        writeLog "Pipe RW verified"
    else
        writeLog "Pipe RW failed"
fi

#Determine PID of jamf process so it can be killed.
trap : SIGTERM SIGINT

echo $$

(jamf policy -event PayloadName ) > /tmp/_out2 &

jamfPID=$!

## Give the policy time to start
sleep 5

#Initialize error counter
counter=0
#While loop to generate package download percent
while [[ $pkgPartial != $pkgTotal ]]
    do
        #Watches for the output of CocoaDialog to equal stopped if "Stop" button is clicked. Cancels install.
        dialogStopCheck=`cat /tmp/_out`
        if [[ "$dialogStopCheck" = "stopped" ]]
            then
                kill $jamfPID
                rm -f /tmp/_out
                rm -f /tmp/_out2
                writeLog "Upgrade cancelled by user."
                exit
        fi
        #Watches for possible jamf binary being upgraded which will fail download of package.
        binaryUpgradeCheck=`cat /private/tmp/_out2 | grep "Upgrading jamf binary..."`
        if [[ "$binaryUpgradeCheck" != "" ]]
            then
                kill $jamfPID
                rm -f /tmp/_out
                rm -f /tmp/_out2
                writeLog "jamf binary upgrade detected. Fixing..."
                jamf policy -event PayloadName >> "$logFile"
                sleep 20
                dialogDownload
        fi
        #Error counter check. Die at 10 errors.
        if [[ $counter -gt 9 ]]
            then
                echo "Package Download failed..." >&3
                sleep 5
                kill $jamfPID
                writeLog "Package download failed."
                exit
            else
                #If the package exists in the download folder determine percentage downloaded.
                if [[ -f  "$downloadFolder"/"$dmgName" ]]
                    then
                        pkgPartial=`du "$downloadFolder"/"$dmgName" | awk '{print $1}'`
                        modifiedTime=`stat -s "$downloadFolder"/"$dmgName" | awk '{print $10}' | sed 's/.*=//'`
                        currentTime=`date +%s`
                        timeDifference=`expr $currentTime - $modifiedTime`
                        #If the downloading file has not been updated in the 10 seconds relative to the current time download may have stalled. Increment error counter.
                        if [[ $timeDifference -gt 10 ]]
                            then
                                echo "$xferPercentage Download may have stalled... ${xferPercentage}%" >&3
                                sleep 5 
                                counter=$(expr $counter + 1)
                            else
                                #Calculate percentage
                                if [[ $pkgPartial =~ ^-?[0-9]+$ ]]
                                    then
                                        xferPercentage=`echo "scale=0; $pkgPartial*100/$pkgTotal" | bc`
                                        echo "$xferPercentage Downloading... ${xferPercentage}%" >&3
                                fi
                        fi
                    #Determine if the file has been moved to the JAMF Waiting room. Exit loop if so to begin install.
                    elif [[ -f "$waitingRoomFolder"/"$dmgName" ]]
                        then
                            writeLog "$dmgName downloaded to $waitingRoomFolder"
                            break
                    else
                        counter=$(expr $counter + 1)
                fi
        fi      
done

## Turn off progress bar by closing file descriptor 3 and removing the named pipe
echo "Closing progress bar."
exec 3>&-
rm -f /tmp/hpipe
}

Forum|alt.badge.img+14
  • Author
  • Valued Contributor
  • 296 replies
  • January 22, 2016

@iJake Thanks so much for your response.

It seems to just pass the script for me and not run any of the cocoadialog stuff. Besides the paths at the top of the script, where else needs to be customized?

Currently, I've just filled in my jamfPID, like so:

(jamf policy -event PayloadName ) > /tmp/_out2 &

jamfPID=863

Am I missing something else? Would a custom trigger go in place of "PayloadName"? Let me know your thoughts. Thanks again.


iJake
Forum|alt.badge.img+21
  • Contributor
  • 279 replies
  • January 22, 2016

Yeah, sorry, I stripped out my policy names. One of the payloads is the cache policy and another is me installing our agent health daemon. You can just strip that whole part since you don't have it. It's where it's checking if an agent upgrade is happening. You'll also need to fill in the DMG file name and its size. You could always get the name dynamically but I didn't have a need at the time.


Forum|alt.badge.img+14
  • Author
  • Valued Contributor
  • 296 replies
  • January 22, 2016

@iJake Thanks and yea I filled in everything at the top, size, name, etc. I was just having trouble seeing where it was kicking off the caching policy.

From what I can see, if it starts to cache, everything else gets ignored until it's done. How are you getting it to start caching and then also kicking off the cocoadialog stuff? Separate launchdaemon?


iJake
Forum|alt.badge.img+21
  • Contributor
  • 279 replies
  • January 22, 2016

This line

(jamf policy -event PayloadName ) > /tmp/_out2 &

It calls the policy, redirects it to that file and then runs its as a background process so the scrip can continue on.

This line

"$cdPath" progressbar --stoppable --title "Upgrade Package Download Progress" --text "Downloading..." --float < /tmp/hpipe > /tmp/_out &

Calls CoocoaDialog with it reading in from the named pipe.

Then anywhere you see >&3 we are sending data to that named pipe.

Let me know if that makes sense. If not, i'd be happy to jump on a WebEx and see if we can get it working for you.


Forum|alt.badge.img+14
  • Author
  • Valued Contributor
  • 296 replies
  • January 22, 2016

@iJake totally makes sense ha. However, I put my custom trigger for the cache policy in place of PayloadName:

(jamf policy -event mytrigger ) > /tmp/_out2 &

and the script basically runs for 2 seconds and finishes. It seems it doesn't call the policy and therefore no progress bars and finishes with nothing happening. If I do "jamf policy -event mytrigger" in terminal on the same mac, the policy works as expected and starts to cache my package. Any ideas?


iJake
Forum|alt.badge.img+21
  • Contributor
  • 279 replies
  • January 22, 2016

First, let's get you the logging function I use so we can see what's going

softwareTitle=cachedDialogTest
logFolder="/Library/Logs/FOLDERNAME"
logFile="$logFolder"/"$softwareTitle.log"
timeStamp=$(date "+%Y %b %d %T")
consoleUser=$(stat -f %Su "/dev/console")
logSize=$(stat -f%z $LogFile)

## LOGGING
writeLog(){ echo "[$timeStamp] [$softwareTitle] [$consoleUser]: $1" >> $logFile; }
[[ -d $logFolder ]] || mkdir -p -m 775 "$logFolder"
[[ $logSize -ge $maxSize ]] && rm -rf "$logFile"

Second, are you making sure to tell it the name of the package you are caching?

dmgName="Package.dmg"

Forum|alt.badge.img+14
  • Author
  • Valued Contributor
  • 296 replies
  • January 22, 2016

@iJake Thanks for your continued responses.

So it makes the log folder but no log inside. The JSS policy log says the policy is successful and just says script exit code 0. So, no logging going on or being made, besides the log folder being created. Again, the script finishes running in about 5 seconds. Running the script from terminal yields the same result.

And yes, package name is correct.


iJake
Forum|alt.badge.img+21
  • Contributor
  • 279 replies
  • January 22, 2016

Run the script locally in bash debug mode.

sudo bash -x scriptName.sh

Forum|alt.badge.img+14
  • Author
  • Valued Contributor
  • 296 replies
  • January 22, 2016

@iJake Here is the output:

+ softwareTitle=cachedDialogTest
+ logFolder=/Library/Logs/jamfcache
+ logFile=/Library/Logs/jamfcache/cachedDialogTest.log
++ date '+%Y %b %d %T'
+ timeStamp='2016 Jan 22 17:00:51'
++ stat -f %Su /dev/console
+ consoleUser=cp
++ stat -f%z
+ logSize=0
+ [[ -d /Library/Logs/jamfcache ]]
+ [[ 0 -ge '' ]]
+ rm -rf /Library/Logs/jamfcache/cachedDialogTest.log
+ cdPath=/Applications/Utilities/cocoaDialog.app/Contents/MacOS/cocoaDialog
+ cdIcon=/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/ApplicationsFolderIcon.icns
+ cdTitle='OS X Installer Cache Progress'
+ pkgTotal=6194494570
+ pkgPartial=0
+ downloadFolder='/Library/Application Support/JAMF/Downloads'
+ waitingRoomFolder='/Library/Application Support/JAMF/Waiting Room'
+ dmgName=InstallOSX_10.11.3_15D21.pkg.zip

And that's where it ends and goes back to the prompt.


iJake
Forum|alt.badge.img+21
  • Contributor
  • 279 replies
  • January 22, 2016

OH. You're never calling the function :)

Put dialogDownload at the part where you want the download and dialog to happen.


Forum|alt.badge.img+14
  • Author
  • Valued Contributor
  • 296 replies
  • January 22, 2016

@iJake Ok I think I'm just tired ha but how would that work exactly? Can you give me an example?


iJake
Forum|alt.badge.img+21
  • Contributor
  • 279 replies
  • January 22, 2016

Sure. So, what i've sent are some variable definition and functions. To make the function work you need to call it. It takes no arguments so all you do is type out dialogDownload much like you would call a variable just without the $ in front of it. You can see that the logging I use is really just a function and its being called by having its name typed out.

Variable Definitions... #definitions go at the top
variable="stuff"
Function Definitions... #then we create the functions
dialogDownlod()
{
lots of code stuffs here
}
dialogDownload #now we call the function

At any point before or after the function you can have other code


Forum|alt.badge.img+14
  • Author
  • Valued Contributor
  • 296 replies
  • January 22, 2016

@iJake Yup that did it. I feel rather foolish. I was just missing the function at the end the whole time.

The only thing that remains is that the progress stays at 0% and never updates. If I stop the cache through the prompt, and then check the file, it's actually 1 or 2 GB already, but before that it just stays stuck at 212k or whatever the first value is. For the value "partialPkg=0" that stays at 0 correct?


iJake
Forum|alt.badge.img+21
  • Contributor
  • 279 replies
  • January 22, 2016

Yeah, it should stay at that 0. Its just the initial definition and we dynamically update it lower. If you run the working script now in debug you should see it doing all the math if its working. Might have to cancel it so you can stop and read the output.

This line does the work to update that number

pkgPartial=`du "$downloadFolder"/"$dmgName" | awk '{print $1}'`

Forum|alt.badge.img+14
  • Author
  • Valued Contributor
  • 296 replies
  • January 22, 2016

@iJake Ok cool.

Well when I go into debug mode in terminal, it just goes crazy and writes forever it seems, all while the progress doesn't move and the display shows 0%. Again, if i cancel it, and click on the file in /Library/Application Support/JAMF/Downloads, it definitely is downloading and usually a GB or two by the time I hit cancel.

Any ideas on what to check?


iJake
Forum|alt.badge.img+21
  • Contributor
  • 279 replies
  • January 22, 2016

@perrycj Copy and paste some of the debug output where it is doing that line i showed above.


Forum|alt.badge.img+14
  • Author
  • Valued Contributor
  • 296 replies
  • January 22, 2016

@iJake sure thing.

++ stat -s '/Library/Application Support/JAMF/Downloads/InstallOSX_10.11.3_15D21.pkg.zip'
++ awk '{print $10}'
++ sed 's/.*=//'
+ modifiedTime=1453503069
++ date +%s
+ currentTime=1453503070
++ expr 1453503070 - 1453503069
+ timeDifference=1
+ [[ 1 -gt 10 ]]
+ [[ 747424 =~ ^-?[0-9]+$ ]]
++ echo 'scale=0; 747424*100/6194494570'
++ bc
+ xferPercentage=0
+ echo '0 Downloading... 0%'
+ [[ 747424 != 6194494570 ]]
++ cat /tmp/_out
+ dialogStopCheck=
+ [[ '' = s	opped ]]
++ cat /private/tmp/_out2
++ grep 'Upgrading jamf binary...'
+ binaryUpgradeCheck=
+ [[ '' != '' ]]
+ [[ 0 -gt 9 ]]
+ [[ -f /Library/Application Support/JAMF/Downloads/InstallOSX_10.11.3_15D21.pkg.zip ]]
++ du '/Library/Application Support/JAMF/Downloads/InstallOSX_10.11.3_15D21.pkg.zip'
++ awk '{print $1}'
+ pkgPartial=747584
++ stat -s '/Library/Application Support/JAMF/Downloads/InstallOSX_10.11.3_15D21.pkg.zip'
++ awk '{print $10}'
++ sed 's/.*=//'
+ modifiedTime=1453503070
++ date +%s
+ currentTime=1453503070
++ expr 1453503070 - 1453503070
+ timeDifference=0
+ [[ 0 -gt 10 ]]
+ [[ 747584 =~ ^-?[0-9]+$ ]]
++ echo 'scale=0; 747584*100/6194494570'
++ bc
+ xferPercentage=0
+ echo '0 Downloading... 0%'
+ [[ 747584 != 6194494570 ]]
++ cat /tmp/_out
+ dialogStopCheck=
+ [[ '' = s	opped ]]
++ cat /private/tmp/_out2
++ grep 'Upgrading jamf binary...'
+ binaryUpgradeCheck=
+ [[ '' != '' ]]
+ [[ 0 -gt 9 ]]
+ [[ -f /Library/Application Support/JAMF/Downloads/InstallOSX_10.11.3_15D21.pkg.zip ]]
++ du '/Library/Application Support/JAMF/Downloads/InstallOSX_10.11.3_15D21.pkg.zip'
++ awk '{print $1}'
+ pkgPartial=747808

It looks like it's getting the partial part but just not updating the %


iJake
Forum|alt.badge.img+21
  • Contributor
  • 279 replies
  • January 22, 2016

I see it doing the math for the percentage, too. Hmm. Try changing

xferPercentage=`echo "scale=0; $pkgPartial*100/$pkgTotal" | bc`
xferPercentage=$(echo "scale=0; $pkgPartial*100/$pkgTotal" | bc)

Reply


Cookie policy

We use cookies to enhance and personalize your experience. If you accept you agree to our full cookie policy. Learn more about our cookies.

 
Cookie settings