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:


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 |
|
| Windows | Powershell |
|
| Batch |
|
|
| 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 |
|
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.
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 |
|
|
|
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

No comments to display
No comments to display