Skip to main content

Script Best Practices

Description

Use these practices to build, test, and troubleshoot Fileset scripts on macOS and Windows without losing the detailed platform examples below.

Don't put passwords in scripts

Fileset scripts are stored locally on managed devices. Do not hard-code usernames, passwords, tokens, or other credentials in the script body.

For example:

Example: password in command

somecommand -u "USERNAME_HERE" -p "PASSSWORD_HERE"

Do not pass passwords as Launch Arguments. Command-line arguments can appear in the process list while the script runs. Use Fileset Environment Variables instead:

leaked_password.pngenvironment_variable_password.png

During script execution, the Launch Argument is seen:

Example: Visible Password

$ ps -ef | grep secure
    0 73010   155   0  9:51am ??         0:00.01 /bin/zsh /var/scripts/532417/unsecure_la.sh secure_password_leaked

Using the example Environment Variables from the image, they would be addressed as:

OS Script Type Command
macOS shell
somecommand -u $username -p $my_pass
Windows Powershell
somecommand -u $Env:username -p $Env:my_pass

Batch
somecommand -u %username% -p %my_pass%

Batch

In order to not transmit the password to a log file accessible on the device, add @echo off before the line containing %my_pass% and @echo on as the next line. Example:

 

@echo off

%SystemRoot%\System32\Reg.exe ADD "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" /v "DefaultPassword" /d "%my_pass%" /t REG_SZ /f 

@echo on

macOS & Windows Python
import os
 
os.getenv('username')
os.getenv('my_pass')

Keep Requirements Scripts Small

FileWave sends a Fileset's Requirements scripts before downloading the remaining Fileset content.

If a Requirements script fails, FileWave does not needlessly download content that the device cannot install.

Kiosk visibility depends on Requirements. A Fileset with failed Requirements does not appear in FileWave Kiosk.

Where possible, avoid piping commands.  This increases overhead on the scripts.  If pipes are required, try to reduce the quantity of pipes.  If nothing else, this makes the scripts easier to read.

$ time system_profiler SPHardwareDataType | grep "Model Identifier" | awk '{print $NF}'
MacBookPro11,4


$ system_profiler SPHardwareDataType | awk '/Model Identifier/ {print $NF}'
MacBookPro11,4

And other commands may achieve the same result more efficiently without the need to pipe.

$ time system_profiler SPHardwareDataType | grep "Model Identifier" | awk '{print $NF}'
MacBookPro11,4

real	0m0.192s
user	0m0.071s
sys	0m0.049s


$ time sysctl -n hw.model
MacBookPro11,4

real	0m0.004s
user	0m0.001s
sys	0m0.002s

Consider this for all scripts beyond just requirement scripts.

Log Script Output

By default, Fileset scripts built through the Scripts button are logged.  All output is redirected to a unique file per script.

If desired, additional information could be redirected to an alternate file.

On macOS, redirecting output to the FileWave Client log makes that output available through Get Log.

On Windows, do not redirect script output into the active FileWave Client log because the client keeps that file locked. Use a separate log file.

Redirecting Output

Output may be redirected using one of the following:

macOS:

echo "hello" >> /tmp/tmp_log_file.log

Windows:

echo "hello" | Out-File -Append -Encoding Ascii C:\Temp\my_temp_file.log

Better than just redirecting output, consider using the tee/Tee-Object command, such that the FileWave generated log and the redirected log both show the output.

macOS:

echo "hello" | tee -a /tmp/tmp_log_file.log

Windows:

echo "hello" | Tee-Object -Variable out | Out-File -InputObject $out -append -encoding Ascii C:\Temp\my_temp_file.log

On macOS, all output can be redirected by using the following at the beginning of the file:

#!/bin/zsh
exec 1>>/var/log/fwcld.log
exec 2>>/var/log/fwcld.log


... rest of script

Testing Scripts

FileWave runs scripts as root on macOS or SYSTEM on Windows. Test in the same account and execution bitness that FileWave will use; interactive-user tests can return different paths, environment variables, permissions, and registry views.

Windows

For example, this command can return different output in a 32-bit Windows process than in a 64-bit process:

 (Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion").EditionId

64bit:

 (Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion").EditionId
Professional

32bit:

 (Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion").EditionId
Enterprise

Similarly, the user executing a script can have an impact on the outcome.  Username itself is a relatively obvious example:

$Env:UserName

In an interactive shell, it reports the signed-in user. Through FileWave, it reports the SYSTEM account.

Current FileWave Windows clients are 64-bit and run scripts as SYSTEM. Filesets created under older behavior may still be configured for 32-bit execution, so test the exact context selected in Fileset Properties. The table records the historical defaults that explain some inherited Filesets:

Version User Bit
FileWave 15.5+ System 64
FileWave 15.4.x- System 32

Current FileWave Windows components are 64-bit. For Fileset scripts, use the execution bitness option when you intentionally need 32-bit or 64-bit behavior.

For upgrade compatibility, scripts or fields created under older behavior may remain configured to run as 32-bit. Review the bitness setting before changing it, because switching an existing script to 64-bit can affect registry paths, filesystem redirection, and available PowerShell modules.

For current Windows Custom Fields, use On Windows, run as 64-bit when 64-bit PowerShell or native Windows components are required. Use 32-bit execution only when the script is written for that context or depends on 32-bit components. See Running Built-in PowerShell Commands with Custom Fields.

PsTools: This relies on downloading and installing, onto the test machine, PsTools.

Running Environment

Open a Windows shell as SYSTEM

Take a look at Getting a CMD prompt as SYSTEM in Windows Vista and Windows Server 2008 for details about running scripts as System.  Note, that by default, this will start an executable as 64-bit, for native 64-bit OS.

From a device with the PsTools installed, start by opening a Command Shell as an Administrator.  From that shell, another command should be run to open yet another shell, but this time in the chosen environment.

The below example shows launching the 32bit version of PowerShell as the System user:

PSEXEC  -i -s -d C:\Windows\SysWOW64\windowsPowerShell\v1.0\powershell.exe

To open a command shell in that same environment would use the following:

PSEXEC -i -s -d %windir%\SysWoW64\cmd.exe

Similarly, when attempting to run some commands, it may be necessary to ensure Windows is using the correct version of a binary with the 'sysnative' redirect.  An example would be Bitlocker's 'manage-bde.exe'.  To use this in a Fileset, try the following:

C:\Windows\sysnative\manage-bde.exe -status

If you have a requirement to run a particular command through the 64-bit version of Powershell this can be achieved as follows: 

If ( [IntPtr]::Size * 8 -ne 64 )
{
    C:\Windows\SysNative\WindowsPowerShell\v1.0\PowerShell.exe -File $MyInvocation.MyCommand.Path
}
Else
{
    # Add code here
}

To test a script intended for the current 64-bit FileWave Client, use the same method but launch a 64-bit application as SYSTEM.

Opening a 64bit version of PowerShell as the System user:

PSEXEC  -i -s -d C:\Windows\System32\windowsPowerShell\v1.0\powershell.exe

Example 32bit to 64bit

The below demonstrates running a 64bit script, but from the 32bit FileWave Client, which will create a new administrator.  Additionally, the FileSet > Get Info > Environment Variables are being used to supply the name and password to the script.

Two Fileset Environment Variables are being supplied, for the user 'rstephens' with a password of 'filewave'

Variable Value
username rstephens
password filewave

Those Parameters may then be referenced from within the script and have them defined for the launching of the 64bit executable within this 32bit script.

Param ( 
    [string]$MyUsername = $Env:username,
    [string]$MyPassword = $Env:password
)


If ( [IntPtr]::Size * 8 -ne 64 )
{
    C:\Windows\SysNative\WindowsPowerShell\v1.0\PowerShell.exe -File $MyInvocation.MyCommand.Path -MyUsername $MyUsername -MyPassword $MyPassword
}
Else
{
    (New-LocalUser -AccountNeverExpires:$true -Password ( ConvertTo-SecureString -AsPlainText -Force $MyPassword) -Name $MyUsername | Add-LocalGroupMember -Group administrators)
}

Troubleshooting PowerShell scripts

In current Fileset Properties, select the execution bitness the script actually requires. Use 64-bit execution for native 64-bit Windows tools, registry paths, and PowerShell modules; retain 32-bit execution only for scripts or components written for that context. If a PowerShell module or cmdlet is missing during deployment but works interactively, reproduce the issue as SYSTEM in the configured bitness before changing the script.

image.png

macOS

On macOS, running commands as sudo is not necessarily the same as actually becoming root.

Root vs As Root

E.g. Run the following commands to evaluate the local variable $HOME, once using sudo and once as root.

$ whoami
auser
$ sudo echo $HOME
/Users/auser
$ sudo su -
$ whoami 
root
$ echo $HOME
/var/root

Paths

Similarly, the paths used to locate executable files will differ, since FileWave is a service ran as root and is not the root account.  On an example device:

User Account Root Account FileWave Client
% echo $PATH | tr ":" "\n"
/opt/homebrew/bin
/opt/homebrew/sbin
/var/root/.cask/bin
/usr/local/sbin
/usr/bin
/bin
/usr/sbin
/sbin

% echo $PATH | tr ":" "\n"
/usr/local/bin
/System/Cryptexes/App/usr/bin
/usr/bin
/bin
/usr/sbin
/sbin
/Applications/VMware Fusion.app/Contents/Public
/Library/Apple/usr/bin
/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin
/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin
/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin

/usr/bin
/bin
/usr/sbin
/sbin

As such, consider always using the full path within a script to an executable, to be explicit, and ensure the executable is found.

For example, it can be seen from the above that homebrew is installed.

 % ls -al /usr/local/bin/brew 
lrwxrwxrwx  1 root  _developer  28 Mar 23  2023 /usr/local/bin/brew -> /usr/local/homebrew/bin/brew

Running the following command would work as the user or root account, but would fail through FileWave, since the FileWave Client does not search /usr/local at all for executables:

brew -v

To ensure the script works and targets the correct brew, the full path should be entered:

/usr/local/bin/brew -v

Plist

It is common to see plist files edited with the 'defaults' command.  However, this command is unique when it comes to ownership and permissions of files.  The 'defaults' command will both take ownership and change permissions of files when used to write to plist files:

$ whoami   
root
$ ls -al /tmp/example_plist.plist
-rw-r--r--  1 rstephens  staff  66 Feb 28 10:03 /tmp/example_plist.plist
$ defaults write /tmp/example_plist Label example_plist
$ ls -al /tmp/example_plist.plist
-rw-------  1 root  wheel  66 Feb 28 10:05 /tmp/example_plist.plist

After using defaults write on an existing plist, restore the intended ownership and permissions. Alternatively, use the following tool when it fits the plist change. Include the full path because /usr/libexec may not be in FileWave's execution path:

/usr/libexec/PlistBuddy

Related Content