Tuesday, March 29, 2022

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 matter how many rows you may return from the script, Tableau Prep Builder will insist that you in fact returned no rows. This is annoying because you can run the same script from a command line and get output.

The fix is that you have to supply at least one row to the script, and then just ignore it entirely. This will placate Tableau Prep Builder and allow execution to continue.

Thursday, February 3, 2022

Tableau Prep Builder: "Why do I have X rows when I export to a .hyper and X*2 rows when I export to Excel? Wait, now it's X*3?"

 Tableau Prep Builder has different default export settings for .hyper and .csv exports versus Excel exports:










Monday, January 31, 2022

Tableau Prep Builder - TabPy configuration in a V1 credentials.json

 The Tableau Prep Builder credentials.json documentation suggests that for a V1 credentials.json, all you need to specify for TabPy support is the password, if any exists. If you do this with a flow that you created using a recent version of Tableau Prep Builder, though, you'll get a ton of errors stating that some field names do not exist, even if the flow functions perfectly from within Prep Builder itself.

Buried within those errors is likely one that indicates that the TabPy configuration is invalid. I originally assumed that Prep Builder saved the TabPy configuration I had entered into the flow itself, but this is not the case. So, the TabPy configuration info isn't in the flow file, and the documentation doesn't indicate how you should enter this info into a V1 credentials.json, so what do you do?

It turns out that you can apparently mix and match parts of the V1 and V2 configurations just fine. The V1 configuration has no way to provide a server hostname and port number, but the V2 configuration does, e.g.:

{

"extensions": {

    "extensionName": "pythonSupport",

    "regular": {

        "host": "localhost",

        "port": "9004"

    }

}

This can happily sit next to a V1 "inputConnections" section. With the V2 extension configuration entered into the credentials.json file, the flow will run correctly.

Thursday, January 20, 2022

Database name in credentials.json for Tableau Prep Builder

Tableau Prep Builder allows you to create a JSON file with database connection information for data sources associated with a flow. There are two different versions of the credentials JSON format. Version 1 looks like this:

  "inputConnections":[

    {

     "hostname":"https://my.server",

     "contentUrl": "mysite",

     "port":443,

     "username": "jsmith",

     "password": "passw0rd$"

    }

 ]

}

Version 2, which you can use with Tableau Prep Builder 2020.3.1 and later, looks like this:

{

 "databaseConnections":[

   {

    "hostname":"example123.redshift.amazonaws.com",

    "port":"5439",

    "username":"jsmith",

    "password":"p@s$w0rd!"

   }

 ]

}

Unfortunately, it doesn't seem like there's a way in the V2 credentials format to specify the name of the database you want to connect to on the server. If you try to add a field like "database" to the connection JSON, you get an unhelpful error:

Unable to read the connections file. Details:

Unrecognized field "databaseConnections" (class com.tableau.tprep.cli.app.connections.ConnectionInfo), not marked as ignorable (3 known properties: "inputConnections", "extensions", "outputConnections"])

 at [Source: (FileInputStream); line: 18, column: 2] (through reference chain: com.tableau.tprep.cli.app.connections.ConnectionInfo["databaseConnections"])

The error suggests that it's looking for the fields for the V1 format, even though I'm using Tableau Prep Builder 2021.4 and the output includes a line which reads "The connection file is in V2 format." Googling didn't turn anything up that helped - just threads on the Tableau support forums of people having similar issues.

If you try to add the database name to the end of the hostname, like in a connection string URL (e.g. "hostname.com/databasename", you get a slightly more helpful error:

We don't have credentials of all connections in tfl/tflx file. The following connection(s) were not found: <database name>

If you throw caution to the wind and try to connect without specifying a database name at all, you get that error again, but it's less helpful this time.

Luckily, the V1 credentials format still works. The field named "contentUrl" can be used to specify the name of the database that you want to connect to. Replace your V2 credentials.json with a V1-formatted one, and it just works.

Thursday, November 7, 2019

Local Group Policy Order of Evaluation Gotcha

It's once again PCI time here at my workplace. While implementing the whitelisting strategy for Windows Apps I described in a previous post, we discovered that while a default state of "Disallowed" blocks things like Notepad from running when you click them in the Start menu, you can still open them either from the Run dialog, or by double-clicking the .exe directly. This is obviously not acceptable from a PCI/whitelisting standpoint, so we had to find some combination of the new Software Restriction Policy (SRP) method and the old User Configuration GPO method to block everything not on the whitelist.

So, we started with our old GPO-based whitelist, and applied the SRP settings on top. To our surprise, when we logged out and back in, we found that while the SRP was blocking apps correctly, it was behaving as if the User GPO was not there at all! Why would this be, when both are turned on at the same time? Does the Computer-level GPO override the User-level GPO?

On a hunch, I tried it the other way around: I started with the SRP settings, then added the User-level GPO on top. This time, it worked! Apps are blocked by the SRP, and .exes are blocked by the User-level GPO.

Why does this happen? These are local GPOs, so the normal Local-Site-Domain-OU precedence rules do not apply as such in this situation. I have not found any documentation describing the order of evaluation/precedence for local GPOs, but it looks like local GPOs are evaluated in the order you create them.

At least in this case. I can't speak for all combinations of GPOs, but if one order doesn't work for you, it may be worth it to try it another way around to see if that makes the precedences line up to do what you want.

Friday, November 1, 2019

Don't forget the simple stuff: HP multi-function printer automatic document feeder malfunction

This morning, a user reported that their HP LaserJet Pro MFP M277fdn would not scan from the automatic document feeder (ADF) on the top of the scanner. This is the tray that you load paper into for sequential copying or scanning. They reported that when they inserted a piece of paper, the screen displayed a message reading "Document loaded", but then when they went to scan it, the printer instructed them to load the document feeder. So, it appeared that the printer both knew and did not know that paper was inserted.

After verifying that this was true, I started down the troubleshooting ladder. I restarted the printer (no change), hard restarted it by shutting down, removing the power cable, and holding the power button (worked once, but then refused to scan the second time), and cleaned within the top door of the ADF (no change). From here, my mind jumped to software or configuration corruption, so I performed a factory reset of the printer. (This led to the discovery that we have no documentation on how this printer was set up, other than phone pictures I took, which provided us with an opportunity to rekindle our tribal knowledge on the subject.) Alas, after everything was set back up, this too was met with no success.

In the smalltalk that the user and I shared while I was working on the problem, she happened to mention that she was somewhat unhappy with the printer anyways, as the scanner platen often gets dirty. It was then that I realized there was one place I didn't clean: the AFD scanning window on the platen. This area is shown in the picture below, taken from HP's support forum.


I had glanced at this area before, and seen a small line of dirt on the window, but thought nothing of it in this instance; usually, if that area is dirty, you'll get bad print quality, but it has never before interfered with scanning. However, since I was running out of ideas, I decided to go ahead and clean that window, and to my surprise, that appears to have fixed the issue.

Why did that help? The scanner clearly knows it has paper loaded, thanks to the microswitch in the AFD that displays the "Document loaded' message, and yet when it comes time to scan, it thought it was empty. I surmise that there must be some optical sensor mechanism (maybe the scanning head itself?) that checks for the presence of paper in the AFD scanning window, regardless of the microswitch status. Thus, when it tried to check for paper, the dirt on the window prevented it from detecting it, and so the printer was confused.

Whatever the cause, this is a good reminder not to forget to check all of the simple stuff first before going to the hard stuff, even if there's not a large chance that it's causing the problem - because it might just be it.

Tuesday, October 29, 2019

Outlook 2016 Not Loading Images on Windows 8

A user today ran into an issue with Outlook 2016 where it would display HTML emails correctly, but almost all images in those emails were broken links, with the red X in the top-left corner in typical Internet Explorer style. We directed her to instructions on how to set Outlook not to block images in messages, but she sent us a screenshot showing that these were not set, which I confirmed when I visited her.

On Google, I found and attempted some solutions, including clearing the Outlook secure temp folder and confirming that Internet Explorer was not set not to save encrypted pages to disk, but these had no effect.

On a hunch, I copied the link to a broken image out of Outlook into Chrome, and found that it loaded the image without issue. Then, I copied the link into Internet Explorer, and lo and behold, received an error message that IE could not connect to the webpage.

This computer was imaged at a time when TLS 1.2 (and, in this user's case, 1.1) were not enabled by default on Windows 8 in Internet Explorer. Chrome loaded the images without issue because it supports these newer versions by default, but Internet Explorer (and therefore, by extension, Outlook) were not so set, so the hosting servers were refusing their connections, and so not serving the images. Once I set IE to enable TLS 1.1 and 1.2, Outlook began loading images immediately.

Ultimately, we will resolve this issue more thoroughly by refreshing the user's computer with one that runs Windows 10, but as a stopgap until that time, this works.

Thursday, September 12, 2019

Unsolved Mysteries: The time Wireshark actually *solved* the problem

We just got back from a ticket for an issue to do with a new lighting control system for one of the theaters on campus. The system includes a layer-3 switch, and the setup process involves assigning a static IP to a laptop that is directly connected via Ethernet to the switch. The theater techs followed the steps in the setup guide to the letter, but no matter how much they would adjust the settings, and even after 45 minutes on the phone with the vendor's tech support, the laptop still could not ping the switch. That's when they called us.

We repeated the setup process, and did our normal network troubleshooting - verifying IP addresses, switching to another network cable, even switching to another laptop - but we didn't have any better luck than they did. Even Nmap couldn't see anything. So, we opened up Wireshark to try to see what the computer and switch may have been trying to send to each other.

Lo and behold, as soon as Wireshark began capturing packets, the laptop and switch could connect to each other, and we were able to use the lighting control program to send configurations to the switch and other equipment. As soon as we stopped the Wireshark capture, though, the problem returned. The only way that these two pieces of hardware can communicate is if Wireshark is running.

This works well enough for what the theater techs need it for, as once the program is uploaded to the equipment, it operates as a self-contained unit. We forwarded our findings on to the vendor's support, and advised the techs to make sure Wireshark is capturing packets if they ever need to connect to the system.

There's no way that Wireshark should help in this way, right?

Wednesday, August 14, 2019

Downloading GarageBand loops for Munki deployment using appleLoops.py

Among other applications, we deploy Apple's GarageBand using Munki. GarageBand can be installed without too much trouble, but on first run, it prompts the user for an administrator password so that it can download and install a set of instrument loop packages. Of course, if the user isn't an administrator - for example, a student in a lab - they won't be able to allow this installation, and will therefore be unable to launch GarageBand.

So, to avoid this problem, it's standard practice to download the loop packages for deployment through something like Munki or Jamf. The traditional way to do this involved opening GarageBand and, before entering your administrator credentials, copying the installer packages out of a path located deep within /var/folders/.... Unfortunately, at least with our modern version of GarageBand 10.3.2, this doesn't seem to be good enough: the set of packages that are downloaded, when installed onto another computer with a fresh install of GarageBand, don't satisfy the application, and it prompts to download them again. It's possible that I made some mistake, but I tried it twice, and it didn't work either time.

Fortunately, some Googling turned up a GitHub repository for a project called appleLoops.py, by Carl Ashley, that ultimately solved our issue. appleLoops.py makes it easy to download loop packages for GarageBand, Logic Pro X, and MainStage 3, and can optionally install these packages in deployment mode, usable from products like Munki and Jamf. It even lets you specify a local cache location that clients will use to download packages so that you can avoid slow or expensive transfers over the Internet.

For our purposes, simply downloading the packages is good enough, so here's how I did it:
1. Download the appleLoops.py project from GitHub.

2. Create a folder to store the loops that you will be downloading. I made this folder as a subdirectory of the appleLoops project folder, but it can be anywhere on your computer, or even a network location to which you have write access.

3. Run the appleLoops.py script:
sudo ./appleLoops.py --apps garageband --destination ./loops --mandatory-only
(If you also want the larger set of optional loops, add --optional-only to the command line. Despite both flags saying "only", they actually do combine without issue.)

4. After the loops have finished downloading, configure your management system of choice to deploy them to clients. If you don't use a management system like Jamf or Munki, you can easily write a shell script to install all of the loops packages manually:
#!/bin/bash

for pkg in $( ls *.pkg ); do
    echo "Installing $pkg"
    installer -pkg $pkg -target /
done

5. After the loops have installed, GarageBand should open without prompting for administrator credentials.

Wednesday, August 7, 2019

Whitelisting Windows Store applications on Windows using secpol.msc

As part of PCI compliance, we are required to restrict the applications which users are able to access on our payment office PCs to those on a specific whitelist. One wrinkle we've run into as part of migrating from Windows 7 to Windows 10 is that our older GPO-based method of restricting applications does not apply to newer Windows Store/Modern apps, including Microsoft Edge and Skype. This wrinkle has, so far, kept our payment PCs on Windows 7 for PCI compliance reasons.

Luckily, I discovered that it is possible to use a different method to whitelist applications: secpol.msc.  This is the Local Security Policy editor, reminiscent of the Local Group Policy editor gpedit.msc, and it allows you to create a software restriction policy to control what applications can and cannot run on a PC.

To create a software restriction policy to create an application whitelist, including Windows Store/Modern apps, follow these steps:

1. Open secpol.msc.

2. Right-click on "Software Restriction Policy", then on "New Software Restriction Policies."

3. In the "Security Levels" folder on the left, right-click on the "Disallowed" setting, then on "Set as default." This sets Windows to disallow access to all applications by default, except for those specifically whitelisted in the "Additional Rules" section below.

4. Right-click in the empty space in the "Additional Rules" folder, then on "New Path Rule...".

5. Enter the path of the application or folder that you wish to control. In this example, I am adding a rule to disallow all applications and apps in the C:\Program Files\WindowsApps folder, which includes things like the Calculator and Notepad apps. Click OK.

6. Add more rules to cover all of the applications you wish to control. As you can see in the screenshot below, I have also added a rule to disallow C:\Windows\SystemApps\*Edge*, in order to block the Microsoft Edge web browser from running. 
Note: You most likely do not want to block C:\Windows\SystemApps as a whole, because Microsoft has app-ified various parts of the operating system, including the Start menu and File Explorer. If you disallow this folder, you will lose access to these parts of the operating system until the policy is relaxed.

That's pretty much it! As you can see in the screenshots above, there are two entries that are automatically created when you create the software restriction policy: one for %HKLM [...] ProgramFilesDir%, and one for %HKLM [...] SystemRoot%. 

%SystemRoot% includes vital parts of the operating system, so it is not advisable to disallow access to this folder on a blanket level. %ProgramFilesDir% covers everything in the Program Files directory, meaning that by default some applications will still be allowed to run, even with the default policy set to disallow - Microsoft Office is an example of this. If you want to block these applications, simply change that entry from unrestricted to disallow.

In order to whitelist an application, you would do the same thing: create a new additional rule, but instead of specifying that it should be a "disallow" rule, choose "unrestricted" instead.

To make your changes take effect, log out and back in.

Monday, July 29, 2019

Windows User Profile Migration with Profwiz

At my new place of employment, we migrate Windows user profiles from one machine to another using the ForensiT Domain Migration User Profile Wizard, or Profwiz for short. This tool works somewhat differently to the Windows Easy Transfer tool, which handles user profile creation and data migration for you, in that Profwiz only configures profiles in-place to work with data copied from another computer. This means that you are free to use whatever format you wish to store the transferred data, versus the Easy Transfer tool, which uses its own proprietary .MIG format, which in the past I have found can become corrupt and unrecoverable over a certain size. Profwiz also does not create the user profile for you, so you need to do that yourself, but once you run Profwiz, everything "just works." An additional plus to Profwiz is that, in order to use the Easy Transfer tool, you must be able to boot and log into the computer, but Profwiz will work as long as the hard drive is readable.

The Profwiz user guide explains in detail the many capabilities of the tool, along with how you can set up a distribution file with many settings pre-entered to save time during the actual migration process. However, if you're just doing a few migrations, it is easy enough to run the tool manually. To perform a user profile migration on Windows with Profwiz, follow these steps:

  1. Create a local profile on the new computer with the same username as on the old computer. If the profile on the old computer was a domain account, don't worry, as Profwiz will take care of that.
  2. Copy the user's profile data from their old hard drive or a backup location to the profile on the new machine that you just created. Make sure to copy AppData and any other hidden folders as well to make sure that things like browser bookmarks and application configurations are migrated correctly. (Be sure to keep the old hard drive for at least a little while as an additional backup!)
  3. Download Profwiz. Note that even though it downloads as an .MSI, it doesn't really install anywhere, and rather just dumps the .EXE in the same directory. You could probably just re-use the same .EXE for future migrations.
  4. Run Profwiz: (Screenshots may be found starting on page 29 of the user guide)
    1. Select the computer to which you are performing the migration. For most cases, you will probably choose "Local Computer", though it is possible to perform migrations over the network. In this guide, I will focus on local migrations. [Next >]
    2. Select the user profile(s) to be migrated. The free Personal edition of Profwiz only allows a single profile to be migrated at a time, but the paid Corporate edition allows you to select several. You will want to select the profile(s) you created and copied data to in the steps above. You may also check the boxes for "Disable Local Accounts" or "Delete Local Accounts" if you wish to remove the local accounts you created after the migration is complete. (This is useful if your environment mandates that only domain accounts be used.) Check the box(es) next to the profile(s) to migrate, and then [Next >].
    3. Enter the name of the domain to which the new user profile belongs. If the computer is not already joined to the domain, you can click the "Join Domain" checkbox below the domain box. If the computer will not be joined to a domain, click the drop-down arrow for the box and select the local computer name. (Azure AD accounts are also supported, but are outside the scope of this writing - please refer to page 38 of the user guide for that information.)
    4. Below the domain box, enter the name of the account that should be used for the new profile. This will usually be the same as the username you gave the new profile when you created it. If desired, check the box for "Set as default logon" (useful for single-user scenarios), then [Next >].
    5. Profwiz will run through several steps to set the appropriate access control list (ACL) settings for the new profile to allow the specified domain user account to access the files and folders contained within, to join the domain, and anything else you specified for it to do. When done, click [Next >].
    6. You will be prompted to reboot the computer after Profwiz is finished.
After the computer reboots, you should be able to log in as the migrated user (assuming that you have their credentials), and everything should be as it was on the old computer. Profwiz does not migrate things like printers or applications, so you will still need to install those by hand, but it does greatly simplify the task of moving a user profile from one machine to another.

Friday, May 10, 2019

Python indentation in the Jamf Pro script editor

The Jamf Pro web interface includes a built-in script editor which allows you to make changes to existing scripts, or to compose new scripts entirely within the Jamf Pro interface. There is one gotcha with this editor that is not really Jamf's fault, and one that experienced programmers can probably see coming: when working with Python scripts, you need to be sure to use consistent indentation, or else you'll see policy failures with errors indicating so.

What happens, at least in my case, is that I will write a Python script in Notepad++, nano, or some other editor that I have set up to replace tab characters with spaces, then copy-and-paste that script into the Jamf Pro web interface. Then, later on, when I go to edit the script, I'll hit the tab key, and the editor dutifully inserts a tab character. This confuses Python, which uses differing levels of indentation to determine code blocks (versus C-style languages, which use curly brackets).

When this happens, you can manually correct the indentation by replacing tabs with spaces, or you can copy-and-paste the indentation from another line at the same level. Another way to get around this, of course, is to copy the script from the Jamf Pro interface into your text editor of choice, make your changes, then copy it back into Jamf Pro, which avoids this issue, but adds some extra work.

This isn't a problem with bash or other scripting languages, as these aren't so strongly influenced by indentation. This also isn't fundamentally a problem with the Jamf Pro script editor, which inserts the character that you're asking it for, but more of a problem with muscle memory working against you when moving between two editors that are not set up in exactly the same way.

Saturday, May 4, 2019

Built-in Unix tools on Windows 10

As you may be aware, Windows 10 supports running a full Linux/bash environment via the Windows Subsystem for Linux (WSL). This can be installed from the Windows Store, or via PowerShell, and gives you access to the tools provided by your chosen distribution without the need to reboot or start up a VM.

However, Windows 10 also (as of April 2018) includes a few very useful tools without even needing to enable the WSL. These tools are:

  • ssh - "Secure SHell", allowing you to access Linux, Unix, macOS, and other related operating systems remotely from the command line
  • curl - "Client URL", which you can use to upload/download files and make requests to web servers from the command line
  • tar - "Tape ARchive", which allows you to combine many files into one archive for ease of transport and/or compression
These tools are now included and enabled by default in Windows 10, and can be accessed from either cmd.exe or PowerShell. One difference between these two shells is in the curl command: in cmd.exe, this runs as a regular executable, with the same flags that you would expect on a Unix machine; however, in PowerShell, curl is an alias to the Invoke-WebRequest cmdlet, so its arguments are not the same. You can get around this by specifying curl.exe in PowerShell to get the standard behavior.

These tools, ssh in particular, are very useful in my daily work as they allow me to remote into Macs without needing to have PuTTY or some other terminal installed. tar is also useful because it helps reduce the slowness you may encounter when copying many small files; by combining these files into one larger archive, you can avoid the time overhead required to start and stop the copying operation for each, only paying that time penalty once.

These tools are also useful for scripting, even (or especially) in batch files. PowerShell provides cmdlets that replicate the functionality of curl, for example, but now that it's included by default, you can easily write a plain old .bat file to download a file from a website, or even to make a HTTP request to a server by POSTing to a URL.

Monday, April 8, 2019

Microsoft Edge on Chromium: First Impressions

I recently saw a Slashdot article that Microsoft just released the first "dev channel" build of its new Chromium-based Edge browser, and figured that I would give it a try and document my first impressions of this "new" browser.

Looks

Visually, the two browsers are very similar. In the screenshot below, Google Chrome is on the left, and Microsoft Edge is on the right. As you can see, the window chromes - the title bars, address bars, etc. - are not very different between the two.


The situation is similar when you click the menu button in the top-right corner of each browser. Microsoft has altered Edge's menu so that it is not exactly reminiscent of either Google Chrome or the old Edge browser, but combines the styles and functionality of the two.
The biggest visual distinction from Chrome that I've seen in my very brief trial of the new Edge is in the settings page. Edge's settings page hardly resembles Chrome's; in fact, it reminds me much more of Firefox's settings page.
The big functionality difference here is that Google Chrome allows you to sign in with a Google account, while Edge allows you to sign in with a Microsoft account. Once you get past the visual differences, though, both settings pages actually work pretty much the same - for example, the "Site Permissions" portion of both offer the same choices of plugins or parts of pages that can be enabled or disabled, and the "About" portion of each shows the version number and automatically tries to install available updates when opened.

Extensions

As you may be able to see in the screenshots above, I have already installed uBlock Origin into the new Edge browser. The interesting and exciting thing is that I did so from the Chrome app store. 

Microsoft provides an extension store for Edge, which in the past would've been the only way to get them. Now, on the new Edge's Extensions page, there is a button in the bottom corner labelled "Allow extensions from other stores." The "Learn More" link gives instructions on how to enable this setting, and how to go to the Chrome Web Store to install extensions. These extensions ask for exactly the same permissions, etc., as in Chrome (of course), and otherwise work identically in both browsers.


Performance

As expected due to the fact that both browsers use the Chromium engine, their scores on the Basemark Web 3.0 test are basically the same: 344.67 for Google Chrome, 354.78 for the new Edge browser. The score breakdown shows that the two are close enough that the point difference doesn't really mean anything, and practically, they (and most web browsers) work well enough that you won't really notice any differences anyway.

Summary

Google Chrome and the new Microsoft Edge web browser, at first impressions, are the same thing. What works in Chrome should work in new Edge, and vice versa - pages will render the same, they will be about as fast as each other, and extensions from Chrome will work in Edge (and, presumably, vice versa with new Edge extensions.)

I'm not sure how I feel about this. On the one hand, as an IT person, it would be great if a clean install of Windows gave me a usable web browser, which will now be available with the new Edge. It's also great that I can use the same extensions in both browsers, and that webpages should look and work the same in both. Microsoft Edge, as hard as it is to believe, is now a good browser.

On the other hand, I don't know if it's good for the web - which is supposed to be a decentralized, diverse system - to lose out on one if its major rendering engines (Edge's EdgeHTML engine is/was a fork of Internet Explorer's Trident.) We all remember the bad old days when it was IE6 or nothing, and webpages employed a lot of not-exactly-standards-compliant techniques to make themselves look and feel right, which caused issues when Firefox and other new browsers came on the scene. Will we see a return to that world when every browser, or at least most, are really just copies of Chrome? Sure, Chromium is open-source, but if everyone uses the same open-source base, the biggest contributors essentially decide the direction of the web. I just hope that Google and its contributors stick to the motto of "don't be evil."

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.

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...