Tuesday, November 10, 2015

Introduction to pkgbuild

Since Apple's PackageMaker went away, people have been looking for a good solid tool to build installer packages.  JAMF's Composer gets a lot of attention and works for many.  However it's not right in all situations, cost being one factor.

Apple supplies pkgbuild with developer tools and, while being a command line tool, is not as hard as one might think.  While pkgbuild is amazingly powerful, I'll only be covering an introduction to it here so you get get a basic app out the door.

Start by building a directory structure.

THE SETUP....
PACKAGEROOT --
   ---ROOTofINSTALLFILES
   ---SCRIPTS

PACKAGEROOT is your folder that keeps all the junk about this stuff in there. I might keep the command line string to build the installer in a .txt file for later reference. 

ROOTofINSTALLFILES is the base installation folder.  If you are installing an Application to the Applications folder, then you either put the Application in this folder and set the install-location to /Applications   or you create a folder called Applications with your program inside it and set the install-location to /.    I personally do that latter, so that if needed, I can later easily add a /Library folder with any LaunchDaemon that I might need to add.  Be sure permissions are correct before building your installer.

SCRIPTS folder contains any postinstall or preinstall scripts and is optional (if you don't have any scripts, ignore it).  These are BASH scripts named simply as postinstall or preinstall with no file extensions and they are executed either before or after the installation takes place.  It's useful if you need to backup data or check for a certain condition before installing anything.  If either scripts exits with a non-zero status, the installer will report as failed.


OTHER INFO YOU'LL NEED...
An Identifier:  you'll need a unique identifier for this package.  Generally a reverse domain name with the package name appended will ensure it is unique.  "com.companyname.packagename.pkg" is a good example.
A Version number: Increment this when you distribute a new version of the installer.  This way computers will know if they are upgrading an existing installer, or repeating something already done.



IF YOU BUILD IT....
Run pkgbuild to build your installer.  Open terminal and 'cd' (change directory) into your PACKAGEROOT.  The easiest way to do that is open terminal and type 'cd' followed by a space.  Then drag the PACKAGEROOT folder into the Terminal window and it'll auto-type the full path for you.

Then type the command:
pgkbuild 
 --root <drag ROOTofINSTALLFILES folder here> 
 --identifier "com.companyname.packagename.pkg" 
 --version "1.0" 
 --install-location "/" 
 --scripts "<drag SCRIPTS folder here>  
  NewInstallerPackageName.pkg

I wrote a little tool to encourage users to update their Adobe CC applications.  Here's an example of my installer build.
pkgbuild 
   --root /Users/todd/Desktop/AdobeCCSoftware/AdobeCCUpdater
   --identifier "edu.school.department.AdobeCCUpdater.pkg" 
   --version "1.0" 
   --install-location "/" 
   --scripts /Users/todd/Desktop/AdobeCCSoftware/Scripts AdobeCCUpdateTool.pkg

Note that these commands are all on one line, only separated here for better formatting. 



TROUBLESHOOTING....Go ahead and run your new installer.  If you have an error, especially with scripts involved, be sure to use the 'echo' command in your script.  While the installer is open, from the Window menu, choose Installer Log and set the PopUp to "Show All Logs".  You can then see exactly what your installer is doing.  I also recommend you test your installer on a test box, not your own machine. 

Also be sure to increment your version number when building a new installer. 



COMING UP....
Another great tool is Apple's pkgutil which allows you to manipulate existing packages.  It's great when you want to see what a postinstall script looks like or what else is included in an installer. 

Friday, October 16, 2015

Empower your Users with Simple Xcode apps

At one point, I worked in a large, distributed environment. Sadly, I didn't have authority over any computer in our management system.  The only way I could take action was if the end user specifically authorized me.  And with 10,000 machines all over the city, that was not trivial to do.  

Actually, it turned out to be surprisingly closer to trivial that I expected.  If you are familiar with the defaults command, I was able to save a setting on a user's computer, to read in with an extension attribute in JAMF Casper and create a smart group.  But how to get the end users to run that Defaults command was the challenge.  

The command I needed was  defaults write /Library/Preferences/com.toddCo.manage.plist AllowUpdates -bool YES.

AllowUpdates means that I'm allowed to push out software updates, such as Office, Java, Flash, and more.  And the end user needed to submit that command on their computer to approve me updating their machines.  

I ended up writing a simple application in Xcode which ran that command for them.  I'll walk you through that process in the remainder of this guide.

Open Xcode and create a new project.  Select Other and choose Cocoa AppleScript.
Give your project a name and enter your Organization Identifier (a reverse domain name is best) and click Next.  Then save the project somewhere appropriate and get ready to do the fun part.

When building an application, it's often best (and most fun) to start with the GUI (Graphic User Interface).  So lets layout our application.  Click on MainMenu.xib on the left, then click the icon for The Window (See circled below) to make sure the main program window appears. 
To start adding elements to our window, click the third icon on the bottom right.  There, you can see buttons, progress bars, check boxes, labels, text fields and more.  Find a Multi-Line Label and put in some text such as "Checking this checkbox will allow the IT Department to update applications such as Java, Flash, or Firefox on your computer".  Then, of course, drag in a check box and set the text to 'I Agree'.

Now to make the 'I Agree' check box do something in code.  Select the check box in your window, then select the Binding Inspector.  That's the 7th icon along the top right section of the screen that looks like a spiral.  See the image below.  If necessary, use the disclosure triangle to expand the 'Value' area.  Check the box to Bind To and choose 'Application'.  And enter a variable name in the Model Key Path field.  I choose to use the variable name 'iAgreeBox'. 


Before we leave this screen, also drag in a Push Button and call it 'Save'.  We'll make that work later. 

Now it's time to write some code to make stuff happen.  Towards the top left of the screen, click on AppDelegate.applescript to bring up the code window.   If we want to get or edit any data in the GUI, we need to reference it with a property.  Since we want to be able to see what that check box is set to, lets create a property for it.  At the top of AppDelegate.applescript, you'll see a 'Property theWindow' line.  Below that, add the text 'property iAgreeBox : false'    The name iAgreeBox needs to match exactly the name you put in that Model Key Path on the previous step.  I often copy/paste just to avoid spelling errors.  Spelling errors don't count if you make the same mistake in both places.  Now we can use iAgreeBox to determine the state of that checkbox in our script. 

Lets add some code for the Save button.  Below the property lines, enter this text:
     on saveTime_(sender)
        do shell script "defaults write /Library/Preferences/com.toddCo.manage.plist AllowUpdates -bool " & iAgreeBox with administrator privileges

         display dialog "Setting Saved!" buttons "OK"


    end saveTime_

Note that there is an underscore after 'saveTime' and the word 'sender' in parens.  That is all required.  Why it's required will be discussed elsewhere.

Lets see what that means in parts.  The first line, on saveTime_(sender) means that this chunk of code should run when the program issues a call to run the task named saveTime.   We'll make that 'Save' button from the GUI do that later.  And at the end, end saveTime_, is the footer, or end, for the steps of the saveTime task.

The  do shell script line is the big one.   Since this is an AppleScript program, we tell Applescript to run some terminal/command line code.  The defaults write command is the one from the beginning of the article.  That's where the Yes or No is saved into that plist file.  With Administrator Privileges means that if needed, the program will prompt for an admin name and password.  Only someone with admin writes will be able to run this program. 

Now we need to link the save button from the GUI to run this chunk of code.  To do that, switch back to MainMenu.xib on the left to see the GUI.  Hold down the Control key on the keyboard and drag from your button to the icon for Delegate.  See the screen image below.  

Once you drag to delegate, a small dark window will appear with a list of those different tasks in it.  One should be called 'saveTime'.   Select it to link the button to that task.  Now when someone clicks the button, the steps in the 'saveTime' block of code will run! 

You now have a fully functional program! How do you know if it works?  Since we're writing some data to a plist, we can just look there and see what it says.

defaults read /Library/Preferences/com.toddco.manage.plist AllowUpdates

Now try running your new program and change the checkbox and click Save.  Each time you open the application, it does default to an 'off' checkbox instead of what is saved there.  We'll fix that next.

Find the section that has applicationWillFinishLaunching.  This is the chunk of code that runs when the program is opened.  Add this line in between so it runs when you start your program.

    on applicationWillFinishLaunching_(aNotification)
         set my iAgreeBox to (do shell script "defaults read /Library/Preferences/com.toddCo.manage.plist AllowUpdates")
    end applicationWillFinishLaunching_


Now when you open the program, the checkbox should match what it was set to last time it was closed.  

 
Next:
In the next post, we will read that setting into our JAMF JSS as an Extension Attribute and create a Smart Group to target those machines. 
http://tmhoule.blogspot.com/2016/05/xcode-applescript-part-ii.html
 

Allow AD based unprivileged users to unlock another user's screensaver

I had a request to allow an AD (Active Directory) group of users, with no admin rights on a Mac, to be able to unlock the screensaver using their credentials.  Here's how I did it.  

$ more /etc/pam.d/screensaver
# screensaver: auth account
auth       optional       pam_krb5.so use_first_pass use_kcminit
auth       required       pam_opendirectory.so use_first_pass nullok
account    required       pam_opendirectory.so
#account    sufficient     pam_self.so
account    required        pam_group.so no_warn group=DOMAIN\AD-Group Name fail_safe

#required       pam_group.so no_warn deny group=admin,wheel ruser fail_safe

Once the screensaver kicks in, press Option-Return to bring up both Name and Password fields.  Then anyone in that Domain\AD-Group Name can unlock the screensaver.

The second to last line enabled the members of the AD group referenced to unlock the screensaver.  So anyone with Admin rights AND members of that AD group can unlock the screensaver.

The pam_self.so line appears to allow the current/local user the ability to unlock the screen saver.  For me, it was ONLY the AD group that can unlock the screensaver.  Not even the current user.  Those were the requirements!

NOTE: in the /etc/pam.d directory are config files for other services that can be configured the same way!  Just always make a backup before playing!

UPDATE: Instead of putting the AD group in teh pam.d file, I've created a local group, and then added the AD group as a member of that using the following command

/usr/sbin/dseditgroup -o edit -a "DOMAIN\AD Group" -t group localgroup

..And that's had good success.   I've also had to create a local user on the machine and add that to my localgroup.