Tuesday, April 2, 2019

Mac Administration with Jamf, Part 5: Composer Package Scripts

As I mentioned in my last post in the "Mac Administration with Jamf" series, Composer not only allows you to package applications for deployment by including files and folders, but also gives you the ability to run scripts at certain points in the install process to achieve various things. 

This only works with .pkg-based installs - .dmg installers don't include any scripts, so if you want to do work other than copying files and folders, you will need to create a .pkg installer. Unfortunately, this also means that installers that use package scripts cannot be indexed and made uninstallable by Jamf, so you will need to create uninstallers yourself if they are needed.

According to the Jamf documentation for Composer, there are a number of triggers for which you can enter scripts. Rich Trouton, on his blog Der Flounder, explains when each of these triggers actually occur, and what can be done with each:
  • Preflight - runs before any files are installed
  • Preinstall - runs before files are installed, but after preflight, and only if this package has never been installed before
  • Preupgrade - runs before files are installed, but after preflight, and only if this package (or an earlier version) has already been installed on the system
  • Postinstall - runs after all files are installed, only if this package has never been installed before
  • Postupgrade - runs after all files are installed, only if this package (or an earlier version) has already been installed on the system
  • Postflight - runs after all files have been installed, and after other postinstallation tasks have occurred
At any stage, if the script does not return with an exit status of zero, Installer will display a message that the installation was not successful, and will exit.

With the new flat-package format for installers, only two of the above triggers are available: preinstall and postinstall. This means that the installer will not automatically detect whether an older version of the package has been installed, so you will need to do any such checks manually.

To add a script to a package in Composer, click the triangle next to the package source name to expand its subfolders. Right-click on the subfolder labelled Scripts, then hover over Add Shell Script (unless you prefer Perl), and select the type of script you wish to add. For this example, I'm adding a postinstall script.



Click on the newly-created script name, and you will be given an editor window into which you can type your script. Enter your script, or copy-and-paste an existing script, then save it with Cmd-S or File > Save. In this screenshot, you can see a script that we use to automatically download, install, and customize the latest version of Google Chrome, which frees us from the need to manually download and package each and every new version as it is released. I will discuss more details about this package, as well as those for Firefox and Adobe Flash Player, in future posts.


Once your scripts are created, build the package as a .pkg installer, and upload and deploy using Jamf like normal. Now, when Jamf runs the installer package, any scripts you entered will run at the appropriate times during the install.

Wednesday, March 20, 2019

Creating local user accounts using PowerShell in a mixed-Windows Server 2012R2/2016 environment

I recently wrote a PowerShell script that, among other things, creates some local user accounts. I developed this script to run on a server running Windows Server 2016, where it worked perfectly. However, when I ran this script on a server running Windows Server 2012R2, it failed with an error that a cmdlet that it used was not found. Specifically, the cmdlet that was not found was New-LocalUser.

As it turns out, versions of PowerShell before v5.1, which is included in Windows Server 2016, there was no PowerShell-native way to create local user accounts. If you need to create a local user in a script on Server 2012R2 or earlier, you will need to use either the ADSI (Active Directory Services Interfaces) or net user commands.

ADSI

ADSI is the Active Directory Services Interface, which provides a COM interface to Active Directory objects and services. Although it sounds like this is only for working with AD, it can also be used to create local accounts by treating the local computer as its own domain. You can use a snippet like this to create a local account using the ADSI PowerShell interface:

$computer = [ADSI]"WinNT://$Env:COMPUTERNAME,Computer"
$account = $computer.Create("User", "username")
$account.SetPassword("password") #or $( Read-Host -Prompt "Password" ) if interactive
$account.SetInfo()
$account.UserFlags = 64 #ADS_UF_PASSWD_CANT_CHANGE
$account.SetInfo()


There are a number of flags that can be set on the new account using the .UserFlags attribute. The MSDN documents don't give numeric values for these flags (though you could calculate them yourself, given knowledge of how binary flags work); thankfully, these can be found online, as in this post from Will Steele on the TechNet forums. You can add flags together either by directly adding their numeric values, or by binary-ORing them together. Be sure to call .SetInfo() on your new local account object to save these changes.

Net User

The net command, or more specifically its subcommand net user, is an older way of creating user accounts from the Windows command line. net user is simple to use by hand, and simple to embed in a script. This command can be called either directly from cmd.exe, or in a PowerShell (or even batch) script with a snippet like this:

net user username password /add [options]

And that's it. This creates the new user, assigns its password, and adds it to the Users group on the local computer. There are various options for this command that can be placed after the final "/add", including /expires, which allows you to specify the date on which the account should expire, and /times, which lets you dictate within what time ranges the account is allowed to log on.

Unfortunately, net user does not have options for everything. For example, you cannot use the net user command to set the account's password to never expire; this must be done using the wmic command, like so:

WMIC USERACCOUNT WHERE Name="username" SET PasswordExpires=FALSE

Putting it together

So, say that you want to write one script that will create local user accounts on servers running Windows Server 2012R2 and 2016, and you want to use the "best" way available on each platform. You can use a snippet like this to detect which version of PowerShell is running, and take the appropriate actions:
if($PSVersionTable.PSVersion.Major < 5 -or ($PSVersionTable.PSVersion.Major >= 5 -and $PSVersionTable.PSVersion.Minor < 1)){
    # New-LocalUser not supported!

    $computer = [ADSI]"WinNT://$Env:COMPUTERNAME,Computer"
    $account = $computer.Create("User", "username")
    $account.SetPassword("password") #or $( Read-Host -Prompt "Password" ) if interactive
    $account.SetInfo()
    $account.UserFlags = 64 #ADS_UF_PASSWD_CANT_CHANGE
    $account.SetInfo()

    #etc.
}
else{
    $password = ConvertTo-SecureString -String "password" #or Read-Host -AsSecureString if interactive
    New-LocalUser -Name "username" -Password $password -PasswordNeverExpires

    #etc.
}

Friday, March 1, 2019

SCCM Gotcha: Task Sequence Media Size and FAT32

In SCCM, you have the option to create Task Sequence Media, which allows you to build a flash drive, DVD, or other bootable media that you can use to deploy operating system task sequences when no network connection is available (specifically, this is "stand-alone media" mode, versus "bootable media" and other options). This is very useful for off-site situations where you may not have any access to your organization's central network, or for computers whose NICs are not working correctly, but still need to be imaged.

In our environment, we tend to use USB flash drives for Task Sequence Media, and I was recently asked to create one for the first time with my newly-granted SCCM Administrator permissions. So, I went through the Create Task Sequence Media wizard, following the steps in the Microsoft docs, but it repeatedly failed, complaining that the USB media did not have enough space available to hold the task sequence. But that didn't make sense - I was using a 64 GB flash drive, and yet when I looked at it in the Disk Management console, I saw that it was only formatted with a single 32 GB partition.

It turns out that the reason for this is that Microsoft does not support partition sizes of greater than 32 GB on FAT32-formatted drives on anything other than Windows NT 3.51. As Wikipedia notes, FAT32 can actually support up to 8 TB or 16 TB partitions, so it's not totally clear why Microsoft chooses to impose this limit. Some conspiratorialists online claim that this was a move to force users onto Microsoft's then-new NTFS filesystem; others say that it has to do with USB flash drives' internal specifications. Whatever the reason, the fact remains that the SCCM Task Sequence Media wizard only offers to format a USB flash drive as FAT32.

This means that if you have a task sequence whose contents are near to or greater than 32 GB, you are likely to encounter the same problems I did. In our case, it is because of the vast range of hardware that our imaging drives must support, causing the driver packages to take up almost 20 GB, but in situations with a lot of software - CAD, Adobe Creative Cloud, etc. -  you could hit the limit with just one driver package.

The solution seems to be not to ask the wizard to create a bootable USB flash drive for you. Instead, choose the second option, "CD/DVD set." The default media size is 4.7GB, but you can increase this, all the way up to "unlimited." Then, using the Browse button, you select the location to which the wizard should save the resulting .ISO file, and continue through the wizard like normal. Once the .ISO is built, you can use a tool like Rufus to copy it to a USB flash drive of an appropriate size and make it bootable. Once you've confirmed that it's working, you can use another tool like ImageUSB to capture a binary copy of that flash drive, and then write it concurrently to a batch of drives.

Tuesday, February 19, 2019

Don't use an ampersand in the title of a macOS installer package

I recently created a macOS installer for an application using Jamf Pro's Composer, and was disappointed to find that when I went to run it, I got an error reading:

The operation couldn't be completed.
(com.apple.installer.pagecontroller error -1.)

I examined the name of the package I had created. This application has an ampersand in its name, and I put its version number, 6.0, at the end of the package name. I know that the period in the version number does not cause any issues, nor do spaces in the name, so the only odd one out is the ampersand. I removed it, rebuilt the package, and found that I could install it without issue.

So, in summary, don't use an ampersand in the title of a macOS installer package, or you'll get a very unhelpful error.

Thursday, February 7, 2019

Barco ClickShare and Microsoft Edge on Windows 10 build 1803+

If you have a computer that upgrades from Windows 10 build 1709 to build 1803 or newer, and Edge stops working, check to see if you have the Barco ClickShare extension pack installed. If you do, uninstall it, and install the updated version 1.1, which Barco claims should resolve this issue.

Thursday, January 31, 2019

Using NoMAD to Replace Traditional AD Binding on macOS

Another day, another problem with Apple's implementation of Active Directory bindings on macOS. Our current issue is that employee laptops, which are bound to AD, do not reliably authenticate over WiFi. We have applied a Configuration Profile that is supposed to use the credentials entered on the login screen to sign in to WiFi, which should then allow it to authenticate to AD, but this does not work consistently, either - only some accounts can log in, and even then only after a few tries. Our networking team has confirmed all of our settings, so it seems like there is some fundamental issue with the combination of macOS, AD bindings, and our WiFi setup.

One potential solution to this chicken-and-egg problem is to create a special account that Macs could use to pre-sign in to WiFi on the login screen, which would allow users to enter their AD credentials and log in just like if they were on Ethernet. You could then use a logon Policy to disconnect from the WiFi network, and then the user would log in with their own credentials. However, this doesn't address some of the other problems that AD-bound Macs have, such as the problems in synchronizing their account credentials with FileVault 2, and the difficulty with SecureTokens in newer versions of macOS if Macs are set up with DEP-based PreStage Enrollments, and therefore never have a local account manually created.

So, since Apple seems to be slowly breaking their AD implementation bit by bit with new features that don't integrate well with it, the more manageable solution is to forgo binding Macs to AD altogether - at least those that will be used primarily on WiFi, or those that need to have FileVault 2 encryption enabled. Local accounts just work better. Unfortunately, it's also a lot of work to make sure that users are updating their local account passwords when their Domain account credentials change, and eventually people may just give up.

NoMAD - from IT's perspective

Enter NoMAD. NoMAD is an application that replaces the traditional AD binding on a Mac with a combination of local accounts and a utility to help synchronize passwords between the two whenever either credential changes. In our implementation, we deploy the NoMAD installer .pkg using Jamf, along with a LaunchAgent to start the application whenever a user logs in, and a default Preferences file with settings provided by NoMAD for customization and setup purposes.

The LaunchAgent script is a simple cat command that creates a .plist file in /Library/LaunchAgents. In this location, the .plist is run for any user who logs into the machine, running under their own session and with their own credentials. Therefore, any changes that may be made in the application are applied only to that user, which is important for an application like NoMAD that deals with AD credentials.

The LaunchAgent script is similar to the following:

#!/bin/bash
cat > /Library/LaunchAgents/com.example.NoMAD.plist <<EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.example.NoMAD</string>
<key>LimitLoadToSessionType</key>
<array>
<string>Aqua</string>
</array>
<key>ProgramArguments</key>
<array>
<string>/Applications/NoMAD.app/Contents/MacOS/NoMAD</string>
</array>
<key>KeepAlive</key>
<true/>
</dict>
</plist>
EOF

NoMAD also supplies a .pkg that creates a LaunchAgent for you, if you prefer to use that instead.

We use another script to create a default preferences file, /Library/Preferences/com.trusourcelabs.NoMAD.plist:

#!/bin/bash
cat > /Library/Preferences/com.trusourcelabs.NoMAD.plist <<EOF
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!DOCTYPE plist PUBLIC "-//Apple/DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>ADDomain</key>
<string>domain.com</string>
<key>KerberosRealm</Key>
<string>DOMAIN.COM</string>
<key>LocalPasswordSync</key>
<true/>
<key>UseKeychain</key>
<true/>
</dict>
</plist>
EOF

So, we combine the NoMAD installer .pkg with the two scripts above into a Policy, then add our laptops into the scope, and at the same time we unbind them from AD to prevent any possible issues. Deployment proceeds as with any other application.

One implication of this setup is that user accounts must be created locally before they can be used, in contrast to AD/mobile accounts that are created on first sign-on. This could be done via SSH or Jamf Remote, or manually, depending on the situation. Happily, since NoMAD handles synchronization of passwords between local accounts and AD, the technician who creates the account can just give it a password like "password", and NoMAD will prompt the user to change it after their first login.

NoMAD - from the user's perspective

The user will probably not interact directly with NoMAD very much - mostly, they only need to use it when their password changes, in order to update their local account to match. On first login, the user enters the local account credentials provided by IT in order to sign in to macOS. Once there, they sign in again, this time into NoMAD, using the menubar icon.


NoMAD prompts for the user's AD credentials, which it then uses to authenticate against the AD server specified in the Preferences file.


Once this is done, the Mac works more or less the same as if it were joined to Active Directory, with the added benefits that things like FileVault 2 password synchronization actually works as it should, since it is actually using local account credentials.

Tuesday, January 15, 2019

Fix "Network accounts are unavailable" on macOS Login Screen with a caffeinate LaunchDaemon

On Macs that are bound to Active Directory, you may from time to time see this popup next to the username box on the macOS login screen:


In most cases, assuming that the Mac is in fact connected to a wired Ethernet connection, this is not actually true, and if you try to log in with your AD credentials you will be successful, and if you wait 30 seconds or so, the message will go away. This message usually appears when the Mac has not been in use for a while and fallen asleep, or after booting up.

It seems that this is probably caused by a decision to show the login screen as quickly as possible, to the point that the network stack and/or AD connection is not fully initialized by the time it is visible. When you try to log in, it kick-starts the OS into loading everything, and should let you right in. However, if you are bothered by this, or have users who are, there is a simple fix that has some other benefits: creating a LaunchDaemon running caffeinate.

caffeinate is a macOS Terminal command that creates an "assertion" to keep certain parts of the computer from going to sleep. These assertions inform the System Management Controller (SMC) that the Mac should not sleep because an application requires it to stay awake, because a user is currently active, etc. Different assertions have different effects, which are reflected in caffeinate's different options. For example, to keep a Mac from putting its display to sleep, you can use caffeinate -d, and, to keep the disk from sleeping, use caffeinate -m.

To keep the Mac from going into idle sleep, which is the state in which the network stack appears to turn off, causing the popup, you can use caffeinate -i for desktop Macs, and caffeinate -s for mobile Macs. This difference is important: -i creates an assertion that does not let the Mac go into idle sleep at all; -s creates an assertion that is only valid when the system is running on AC power, meaning that a laptop will not stay awake in a bag, or other potentially damaging situation, as long as it is disconnected from its charger.

You can also use the caffeinate command to create an assertion that is only valid for a certain number of seconds, with caffeinate -t <seconds> -<mode>, or to create one that stays valid as long as a certain command is running, such as caffeinate -i make to keep the Mac awake as long as make is running.

So, with all of that out of the way, how can you use caffeinate to prevent that message from popping up on the login screen? Use a LaunchDaemon. LaunchDaemons run at system startup, and continue running regardless of whether a user is on the machine or not. I use a LaunchDaemon like this, located at /Library/LaunchDaemons, to keep the popup from appearing on our lab desktop Macs:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE plist PUBLIC "-//Apple Computer/DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<dict>
<key>Label</key>
<string>com.example.caffeinate.plist</string>
<key>Program</key>
<string>/usr/bin/caffeinate</string>
<key>ProgramArguments</key>
<array>
    <string>/usr/bin/caffeinate</string>
    <string>-s</string>
</array>
<key>RunAtLoad</key>
<true/>
</dict>
</plist>

Install that LaunchDaemon and reboot the Mac to make it take effect. The popup will still appear for 30 seconds or so after a reboot, as the OS hasn't had a chance to initialize everything yet, but it should not reappear afterwards. This still allows the screen to go to sleep, as well as the HDDs, but keeps the rest of the OS awake so that the popup does not appear.

Since this keeps the networking components of the OS awake, it also helps resolve situations where pinging a Mac that has gone to sleep can take several tries as it tries to wake up. This, therefore, makes the process of getting Macs to check in to Jamf or another management system more reliable.

Tableau, TabPy, and the Case of No Input Rows

 I haven't scientifically confirmed this or anything, but it sure seems like if you pass an empty dataframe to a TabPy script, then no m...