├── Baseline.png
├── Build
├── Scripts
│ └── ExampleScript.sh
├── Packages
│ ├── .DS_Store
│ └── ExamplePKG.pkg
├── Icons
│ └── ExampleIcon.png
├── com.secondsonconsulting.baseline.plist
├── Baseline_daemon-preinstall.sh
└── Baseline_daemon-postinstall.sh
├── .gitignore
├── ProfileManifest
├── README.md
├── com.secondsonconsulting.baseline.json
└── com.secondsonconsulting.baseline.plist
├── LICENSE
├── ExampleConfigurationFiles
├── BaselineConfig.plist
├── Baseline-BasicExample.mobileconfig
└── Baseline-ProductionExample.mobileconfig
├── README.md
├── ExampleInitialScripts
└── InitialScriptExample-1.sh
└── Baseline.sh
/Baseline.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SecondSonConsulting/Baseline/HEAD/Baseline.png
--------------------------------------------------------------------------------
/Build/Scripts/ExampleScript.sh:
--------------------------------------------------------------------------------
1 | #!/bin/zsh
2 | #set -x
3 |
4 | echo "I am an example script: $0"
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | Build/.DS_Store
3 | .DS_Store
4 | .DS_Store
5 | Build/.DS_Store
6 | TestConfig.plist
7 |
--------------------------------------------------------------------------------
/Build/Packages/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SecondSonConsulting/Baseline/HEAD/Build/Packages/.DS_Store
--------------------------------------------------------------------------------
/Build/Icons/ExampleIcon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SecondSonConsulting/Baseline/HEAD/Build/Icons/ExampleIcon.png
--------------------------------------------------------------------------------
/Build/Packages/ExamplePKG.pkg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SecondSonConsulting/Baseline/HEAD/Build/Packages/ExamplePKG.pkg
--------------------------------------------------------------------------------
/Build/com.secondsonconsulting.baseline.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | AbandonProcessGroup
6 |
7 | Label
8 | com.secondsonconsulting.baseline
9 | Program
10 | /usr/local/Baseline/Baseline.sh
11 | RunAtLoad
12 |
13 | StandardErrorPath
14 | /var/log/BaselineOutput.log
15 | StandardOutPath
16 | /var/log/BaselineOutput.log
17 |
18 |
19 |
--------------------------------------------------------------------------------
/ProfileManifest/README.md:
--------------------------------------------------------------------------------
1 | # These are for reference and beta only
2 |
3 | These files may not match what's in the official public ProfileManifest repositories. They may be out of date, or they may be ahead of pull requests on the official repos. Check the modified date and use with caution.
4 |
5 | For JSONs or Manifests to use with Beta features, check this folder in the `dev` branch.
6 |
7 | [The office ProfileManifest repository is the proper place to get the latest public release.](https://github.com/ProfileCreator/ProfileManifests/blob/master/Manifests/ManagedPreferencesApplications/com.secondsonconsulting.baseline.plist)
8 |
9 | [Jamf JSON Schema](https://github.com/Jamf-Custom-Profile-Schemas/ProfileManifestsMirror/blob/main/manifests/ManagedPreferencesApplications/com.secondsonconsulting.baseline.json)
10 |
--------------------------------------------------------------------------------
/Build/Baseline_daemon-preinstall.sh:
--------------------------------------------------------------------------------
1 | #!/bin/zsh --no-rcs
2 |
3 | # Function to exit with an error message
4 | exit_with_error() {
5 | echo "Error: $1" >&2
6 | exit 1
7 | }
8 |
9 | # Set path and name variables
10 | launchDPath="/Library/LaunchDaemons"
11 | launchDName="com.secondsonconsulting.baseline"
12 |
13 | # Check if the launch daemon is running
14 | launchctl list "$launchDName" > /dev/null 2>&1
15 | list_result=$?
16 |
17 | # If daemon is running, attempt to unload and remove it
18 | if [ "$list_result" -eq 0 ]; then
19 | launchctl bootout system/"$launchDName"
20 | bootout_result=$?
21 |
22 | if [ "$bootout_result" -ne 0 ]; then
23 | exit_with_error "Failed to unload the launch daemon."
24 | fi
25 |
26 | # Remove plist file
27 | rm "$launchDPath/$launchDName.plist" || true
28 | fi
29 |
30 | # Successful exit
31 | exit 0
32 |
--------------------------------------------------------------------------------
/Build/Baseline_daemon-postinstall.sh:
--------------------------------------------------------------------------------
1 | #!/bin/zsh --no-rcs
2 |
3 | # Set variables for path/name
4 | launchDPath="/Library/LaunchDaemons"
5 | launchDName="com.secondsonconsulting.baseline"
6 |
7 | # Function to exit with an error message
8 | exit_with_error() {
9 | echo "Error: $1" >&2
10 | exit 1
11 | }
12 |
13 | # Load the launch daemon
14 | launchctl bootstrap system "$launchDPath/$launchDName.plist" > /dev/null 2>&1
15 | bootstrap_result=$?
16 |
17 | # Test if the bootstrap command failed
18 | if [ "$bootstrap_result" -ne 0 ]; then
19 | exit_with_error "Failed to load the launch daemon."
20 | fi
21 |
22 | # Check if the launch daemon is actually running
23 | launchctl list "$launchDName" > /dev/null 2>&1
24 | list_result=$?
25 |
26 | # Exit fail if the launch daemon isn't loaded
27 | if [ "$list_result" -ne 0 ]; then
28 | exit_with_error "Launch daemon is not running."
29 | fi
30 |
31 | # Successful exit
32 | exit 0
33 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 Second Son Consulting
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/ExampleConfigurationFiles/BaselineConfig.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | InitialScripts
6 |
7 |
8 | Arguments
9 | --fdsa
10 | DisplayName
11 | Initial Example Script
12 | MD5
13 | e567257026d6032dd232df75fd3ba500
14 | ScriptPath
15 | ExampleScript.sh
16 |
17 |
18 | Installomator
19 |
20 |
21 | DisplayName
22 | Firefox
23 | Label
24 | firefoxpkg
25 |
26 |
27 | DisplayName
28 | Dockutil
29 | Label
30 | dockutil
31 |
32 |
33 | Packages
34 |
35 |
36 | DisplayName
37 | Example Package
38 | PackagePath
39 | ExamplePKG.pkg
40 | TeamID
41 | 7Q6XP5698G
42 |
43 |
44 | Scripts
45 |
46 |
47 | Arguments
48 | --asdf
49 | DisplayName
50 | Example Script
51 | MD5
52 | e567257026d6032dd232df75fd3ba500
53 | ScriptPath
54 | ExampleScript.sh
55 |
56 |
57 |
58 |
59 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 | An MDM agnostic zero touch or light touch solution for macOS.
3 |
4 | By leveraging swiftDialog, Installomator, and original code, Baseline provides an automated way to install applications and run scripts. Configure the behavior of Baseline via a mobileconfig or plist file. Baseline will install packages, scripts, and Installomator labels as defined in the configuration file.
5 |
6 | ## Requirements
7 | - macOS 12 or newer*
8 | - An installation PKG for the project. Either the provided PKG or your own.
9 | - A configuration profile defining what Installomator labels, Packages, and/or Scripts you wish to run
10 |
11 | _*This requirement is based on the latest version requirements for swiftDialog. Older versions of swiftDialog supported macOS 11, and Baseline can be used with macOS 11 if you deploy an older swiftDialog prior to running Baseline._
12 |
13 | ## Visit the Wiki
14 | Detailed documentation on how Baseline works and how to configure it can be found in the [wiki](https://github.com/SecondSonConsulting/Baseline/wiki).
15 |
16 | ## Thank you to the Mac Admins Community
17 | This project wouldn’t be possible without the amazing hard work provided to the Mac Admins community. Bart Reardon, Søren Theilgaard, Armin Briegel, Adam Codega, Dan Snelson, Pico Mitchell, and all of the other amazing people maintaining and testing swiftDialog, Installomator, and other community tools.
18 | We are happy to have the opportunity to give back, and hope other Mac Admins might find this project useful.
19 |
20 | Thank you to Drew Diver and Mykola for feature enhancements on this project.
21 |
22 | [macos](https://icons8.com/icon/80591/apple-logo) icon by [Icons8](https://icons8.com) used for example.
23 |
--------------------------------------------------------------------------------
/ExampleConfigurationFiles/Baseline-BasicExample.mobileconfig:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PayloadContent
6 |
7 |
8 | InitialScripts
9 |
10 |
11 | Arguments
12 | --fdsa
13 | DisplayName
14 | Initial Example Script
15 | MD5
16 | e567257026d6032dd232df75fd3ba500
17 | ScriptPath
18 | ExampleScript.sh
19 |
20 |
21 | Installomator
22 |
23 |
24 | DisplayName
25 | Firefox
26 | Label
27 | firefoxpkg
28 |
29 |
30 | DisplayName
31 | Dockutil
32 | Label
33 | dockutil
34 |
35 |
36 | Packages
37 |
38 |
39 | DisplayName
40 | Example Package
41 | PackagePath
42 | ExamplePKG.pkg
43 | TeamID
44 | 7Q6XP5698G
45 |
46 |
47 | PayloadDisplayName
48 | Baseline
49 | PayloadIdentifier
50 | com.secondsonconsulting.baseline.D031FEC3-06B0-497A-BC0A-5D24AE8E91AD
51 | PayloadType
52 | com.secondsonconsulting.baseline
53 | PayloadUUID
54 | F46B02DA-FF67-481D-B39C-485F8CC9E39B
55 | PayloadVersion
56 | 1
57 | Scripts
58 |
59 |
60 | Arguments
61 | --asdf
62 | DisplayName
63 | Example Script
64 | MD5
65 | e567257026d6032dd232df75fd3ba500
66 | ScriptPath
67 | ExampleScript.sh
68 |
69 |
70 |
71 |
72 | PayloadDescription
73 | Configuration file for Baseline.sh
74 | PayloadDisplayName
75 | Baseline Config
76 | PayloadIdentifier
77 | SECONDSON.6F1AD41D-2069-4861-B72C-C5F0D44C9B99
78 | PayloadOrganization
79 | Second Son Consulting Inc.
80 | PayloadScope
81 | System
82 | PayloadType
83 | Configuration
84 | PayloadUUID
85 | 8A016FF5-057E-4F31-9043-9C715F061433
86 | PayloadVersion
87 | 1
88 |
89 |
90 |
--------------------------------------------------------------------------------
/ExampleConfigurationFiles/Baseline-ProductionExample.mobileconfig:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PayloadContent
6 |
7 |
8 | Installomator
9 |
10 |
11 | DisplayName
12 | Firefox
13 | Icon
14 | ExampleIcon.png
15 | Label
16 | firefoxpkg
17 |
18 |
19 | DisplayName
20 | Microsoft Office 365
21 | Icon
22 | SF=doc.circle,color=blue
23 | Label
24 | microsoftoffice365
25 |
26 |
27 | DisplayName
28 | Zoom
29 | Icon
30 | SF= video.circle,color=blue
31 | Label
32 | zoom
33 |
34 |
35 | Packages
36 |
37 |
38 | DisplayName
39 | Mist v0.5
40 | Icon
41 | https://github.com/ninxsoft/Mist/blob/main/README%20Resources/App%20Icon.png?raw=true
42 | PackagePath
43 | https://github.com/ninxsoft/Mist/releases/download/v0.5/Mist.0.5.pkg
44 | TeamID
45 | 7K3HVCLV7Z
46 |
47 |
48 | PayloadDisplayName
49 | Baseline
50 | PayloadIdentifier
51 | com.secondsonconsulting.baseline.D031FEC3-06B0-497A-BC0A-5D24AE8E91AD
52 | PayloadType
53 | com.secondsonconsulting.baseline
54 | PayloadUUID
55 | 75A6163E-E581-4E91-88B7-17927BF9667A
56 | PayloadVersion
57 | 1
58 | Scripts
59 |
60 |
61 | DisplayName
62 | Enabling Location Services
63 | Icon
64 | /System/Library/PrivateFrameworks/AOSUI.framework/Versions/A/Resources/findmy.icns
65 | MD5
66 | c32364f1f3f7fc2410e265d82095feee
67 | ScriptPath
68 | https://raw.githubusercontent.com/acodega/macos/main/enable_location-services.sh
69 |
70 |
71 |
72 |
73 | PayloadDescription
74 | Configuration file for Baseline.sh
75 | PayloadDisplayName
76 | Baseline Config
77 | PayloadIdentifier
78 | SecondSon.6F1AD41D-2069-4861-B72C-C5F0D44C9B99
79 | PayloadOrganization
80 | Second Son Consulting Inc.
81 | PayloadScope
82 | System
83 | PayloadType
84 | Configuration
85 | PayloadUUID
86 | BEDFC054-5B20-430F-B388-A51C3B4D30AE
87 | PayloadVersion
88 | 1
89 |
90 |
91 |
--------------------------------------------------------------------------------
/ExampleInitialScripts/InitialScriptExample-1.sh:
--------------------------------------------------------------------------------
1 | #!/bin/zsh
2 | # Uncomment this for verbose debug
3 | #set -x
4 |
5 | ##########
6 | # This is an example script designed to show some of the features of Baseline.
7 | # Written by: Trevor Sysock of Second Son Consulting
8 | # @BigMacAdmin on Github and Slack
9 | #
10 | # In this example, we're making an InitialScript that will prompt the user to choose their
11 | # department and then rename the computer and put a script in place to be processed by Baseline
12 | # according to the user's response.
13 | #
14 | # This script assumes you have the following in place:
15 | # - A Baseline configuration file containing this script in the "InitialScripts" section and "DepartmentSetup.sh" in the "Scripts" section.
16 | # - One or more scripts named to match the abbreviation of the department the user chooses.
17 | # - /usr/local/Baseline/Scripts/
18 | # - Acct.sh
19 | # - Sales.sh
20 | # - Exec.sh
21 | # - Art.sh
22 | # - Tech.sh
23 | # - A "default" script in the Baseline Scripts directory name "DepartmentSetup.sh"
24 | #
25 | # When the user chooses a department, the script associated with that department is renamed "DepartmentSetup.sh", and will be processed by Baseline.
26 |
27 | #############
28 | # Functions #
29 | #############
30 |
31 | # Verify we are running as root
32 | if [[ $(id -u) -ne 0 ]]; then
33 | echo "ERROR: This script must be run as root **EXITING**"
34 | exit 1
35 | fi
36 |
37 | #############
38 | # Variables #
39 | #############
40 |
41 | # Company name or abbreviation used for naming the computer
42 | companyName="Acme"
43 |
44 | # Path to SwiftDialog
45 | dialogPath='/usr/local/bin/dialog'
46 |
47 | # Path to Baseline Scripts directory
48 | BaselineScripts="/usr/local/Baseline/Scripts"
49 |
50 | # Get the shortname of the current user
51 | currentUser=$( echo "show State:/Users/ConsoleUser" | scutil | awk '/Name :/ { print $3 }' )
52 | # Get the UID of the current user
53 | currentUserUID=$(/usr/bin/id -u "$currentUser")
54 | # Get the Display Name of the current user
55 | currentUserDisplayName=$(id -F "$currentUser")
56 | # Get the First Name of the current user
57 | firstName=$(/usr/bin/id -F "$currentUser"| cut -d ' ' -f 1)
58 |
59 | # Setup your welcome experience
60 | welcomeTitle="Help Us Name Your Mac."
61 | welcomeBody="Hi $currentUserDisplayName, lets get your computer setup.\n\n To start, please choose your department."
62 | departmentChoices="Art,Sales,Accounting,Executive,Technician"
63 |
64 | ######################
65 | # Script Starts Here #
66 | ######################
67 |
68 | # Call the dialog command and put the results in a variable, so we can check against them afterwards
69 | departmentSelection=$("$dialogPath" \
70 | --title "$welcomeTitle" \
71 | --message "$welcomeBody" \
72 | --selecttitle "Department" \
73 | --ontop \
74 | --blurscreen \
75 | --position top \
76 | --selectvalues "$departmentChoices")
77 |
78 | # Set the Department Abbreviation based on user input
79 | case $departmentSelection in
80 |
81 | *'"Department" : "Accounting"'*)
82 | departmentAbbr="Acct"
83 | ;;
84 | *'"Department" : "Sales"'*)
85 | departmentAbbr="Sales"
86 | ;;
87 | *'"Department" : "Executive"'*)
88 | departmentAbbr="Exec"
89 | ;;
90 | *'"Department" : "Art"'*)
91 | departmentAbbr="Art"
92 | ;;
93 | *'"Department" : "Technician"'*)
94 | departmentAbbr="Tech"
95 | ;;
96 | esac
97 |
98 | # Set the device name variable
99 | finalComputerName="$companyName"-"$departmentAbbr"-"$firstName"
100 |
101 | # Name the computer 3 ways
102 | scutil --set ComputerName $finalComputerName
103 | scutil --set HostName "$finalComputerName".shared
104 | scutil --set LocalHostName $finalComputerName
105 |
106 | # Check if there is a department script configured. If there is, rename it so that Baseline processes it.
107 | if [ -e "$BaselineScripts"/"$departmentAbbr".sh ]; then
108 | mv "$BaselineScripts"/"$departmentAbbr".sh "$BaselineScripts"/DepartmentSetup.sh
109 | fi
110 |
--------------------------------------------------------------------------------
/ProfileManifest/com.secondsonconsulting.baseline.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "Baseline by Second Son Consulting (com.secondsonconsulting.baseline)",
3 | "description": "Baseline by Second Son Consulting Configuration",
4 | "properties": {
5 | "InitialScripts": {
6 | "type": "array",
7 | "items": {
8 | "type": "object",
9 | "properties": {
10 | "DisplayName": {
11 | "type": "string",
12 | "title": "Display Name",
13 | "description": "The name you want to appear in SwiftDialog and logs. Initial Scripts will only be shown to the user if the item Fails."
14 | },
15 | "ScriptPath": {
16 | "type": "string",
17 | "title": "Script Path",
18 | "description": "A path to the script you want to run. Can be a local file path or a URL."
19 | },
20 | "SHA256": {
21 | "type": "string",
22 | "title": "SHA256",
23 | "description": "The expected SHA256 of the script being run."
24 | },
25 | "MD5": {
26 | "type": "string",
27 | "title": "MD5",
28 | "description": "The expected MD5 of the script being run."
29 | },
30 | "Arguments": {
31 | "type": "string",
32 | "title": "Arguments",
33 | "description": "Arguments you want to pass to the script when it is run."
34 | },
35 | "AsUser": {
36 | "type": "boolean",
37 | "title": "Run as User",
38 | "description": "Enable this option to run the script as the logged in user."
39 | }
40 | }
41 | },
42 | "title": "InitialScripts",
43 | "description": "Define scripts that are run immediately as the user logs in, prior to any Baseline SwiftDialog windows. These scripts are the place to add your custom welcome messages or pre-flight scripts.",
44 | "property_order": 5
45 | },
46 | "Installomator": {
47 | "type": "array",
48 | "items": {
49 | "type": "object",
50 | "properties": {
51 | "DisplayName": {
52 | "type": "string",
53 | "title": "Display Name",
54 | "description": "The name you want to appear in the SwiftDialog menu as this Installomator label is processed."
55 | },
56 | "Label": {
57 | "type": "string",
58 | "title": "Label",
59 | "description": "The Installomator label."
60 | },
61 | "Arguments": {
62 | "type": "string",
63 | "title": "Arguments",
64 | "description": "Arguments you want to pass to the Installomator command for this label."
65 | },
66 | "Icon": {
67 | "type": "string",
68 | "title": "Icon",
69 | "description": "The icon you want to appear in the SwiftDialog menu as this Installomator label is processed. Can be a URL or local file path."
70 | },
71 | "Subtitle": {
72 | "type": "string",
73 | "title": "Subtitle",
74 | "description": "The subtitle text to appear on this line item."
75 | }
76 | }
77 | },
78 | "title": "Installomator Labels",
79 | "description": "Define Installomator labels to be run by Baseline. For information specific to Installomator, see the Installomator Github: https://github.com/Installomator/Installomator",
80 | "property_order": 10
81 | },
82 | "Packages": {
83 | "type": "array",
84 | "items": {
85 | "type": "object",
86 | "properties": {
87 | "DisplayName": {
88 | "type": "string",
89 | "title": "Display Name",
90 | "description": "The name you want to appear in the SwiftDialog menu as this package is installed."
91 | },
92 | "PackagePath": {
93 | "type": "string",
94 | "title": "Package Path",
95 | "description": "A path to the package you want to install. Can be a local file path or a URL."
96 | },
97 | "TeamID": {
98 | "type": "string",
99 | "title": "TeamID",
100 | "description": "The expected TeamID of the package being installed."
101 | },
102 | "SHA256": {
103 | "type": "string",
104 | "title": "SHA256",
105 | "description": "The expected SHA256 of the package being installed."
106 | },
107 | "MD5": {
108 | "type": "string",
109 | "title": "MD5",
110 | "description": "The expected MD5 of the package being installed."
111 | },
112 | "Arguments": {
113 | "type": "string",
114 | "title": "Arguments",
115 | "description": "Arguments you want to pass to the installer command for this package."
116 | },
117 | "Icon": {
118 | "type": "string",
119 | "title": "Icon",
120 | "description": "The icon you want to appear in the SwiftDialog menu as this package is installed. Can be a URL or local file path."
121 | },
122 | "Subtitle": {
123 | "type": "string",
124 | "title": "Subtitle",
125 | "description": "The subtitle text to appear on this line item."
126 | }
127 | }
128 | },
129 | "title": "Packages",
130 | "description": "Define Packages to be run by Baseline",
131 | "property_order": 15
132 | },
133 | "Scripts": {
134 | "type": "array",
135 | "items": {
136 | "type": "object",
137 | "properties": {
138 | "DisplayName": {
139 | "type": "string",
140 | "title": "Display Name",
141 | "description": "The name you want to appear in the SwiftDialog menu as this script is run."
142 | },
143 | "ScriptPath": {
144 | "type": "string",
145 | "title": "Script Path",
146 | "description": "A path to the script you want to run. Can be a local file path or a URL."
147 | },
148 | "SHA256": {
149 | "type": "string",
150 | "title": "SHA256",
151 | "description": "The expected SHA256 of the script being run."
152 | },
153 | "MD5": {
154 | "type": "string",
155 | "title": "MD5",
156 | "description": "The expected MD5 of the script being run."
157 | },
158 | "Arguments": {
159 | "type": "string",
160 | "title": "Arguments",
161 | "description": "Arguments you want to pass to the script when it is run."
162 | },
163 | "AsUser": {
164 | "type": "boolean",
165 | "title": "Run as User",
166 | "description": "Enable this option to run the script as the logged in user."
167 | },
168 | "Icon": {
169 | "type": "string",
170 | "title": "Icon",
171 | "description": "The icon you want to appear in the SwiftDialog menu as this script is run. Can be a URL or local file path."
172 | },
173 | "Subtitle": {
174 | "type": "string",
175 | "title": "Subtitle",
176 | "description": "The subtitle text to appear on this line item."
177 | }
178 | }
179 | },
180 | "title": "Scripts",
181 | "description": "Define Scripts to be run by Baseline",
182 | "property_order": 20
183 | },
184 | "WaitFor": {
185 | "type": "array",
186 | "items": {
187 | "type": "object",
188 | "properties": {
189 | "DisplayName": {
190 | "type": "string",
191 | "title": "Display Name",
192 | "description": "The name you want to appear in the SwiftDialog menu for this item we are waiting for."
193 | },
194 | "Path": {
195 | "type": "string",
196 | "title": "Path",
197 | "description": "A path to the file you want Baseline to wait for."
198 | },
199 | "Icon": {
200 | "type": "string",
201 | "title": "Icon",
202 | "description": "The icon you want to appear in the SwiftDialog menu for this item. Can be a URL or local file path."
203 | },
204 | "Subtitle": {
205 | "type": "string",
206 | "title": "Subtitle",
207 | "description": "The subtitle text to appear on this line item."
208 | }
209 | }
210 | },
211 | "title": "WaitFor",
212 | "description": "Define files which you want Baseline to wait for. Use this for items not directly installed by Baseline, like VPP or MDM installed apps.",
213 | "property_order": 25
214 | },
215 | "FinalScripts": {
216 | "type": "array",
217 | "items": {
218 | "type": "object",
219 | "properties": {
220 | "DisplayName": {
221 | "type": "string",
222 | "title": "Display Name",
223 | "description": "The name you want to appear SwiftDialog and logs. Final Scripts will only be shown to the user if the item Fails."
224 | },
225 | "ScriptPath": {
226 | "type": "string",
227 | "title": "Script Path",
228 | "description": "A path to the script you want to run. Can be a local file path or a URL."
229 | },
230 | "SHA256": {
231 | "type": "string",
232 | "title": "SHA256",
233 | "description": "The expected SHA256 of the script being run."
234 | },
235 | "MD5": {
236 | "type": "string",
237 | "title": "MD5",
238 | "description": "The expected MD5 of the script being run."
239 | },
240 | "Arguments": {
241 | "type": "string",
242 | "title": "Arguments",
243 | "description": "Arguments you want to pass to the script when it is run."
244 | },
245 | "AsUser": {
246 | "type": "boolean",
247 | "title": "Run as User",
248 | "description": "Enable this option to run the script as the logged in user."
249 | }
250 | }
251 | },
252 | "title": "FinalScripts",
253 | "description": "Define scripts that are run just before completion dialogs. For reporting webhooks, cleanup tasks, etc. Similar to InitialScripts, these items do not appear on the List View.",
254 | "property_order": 30
255 | },
256 | "WaitForTimeout": {
257 | "type": "integer",
258 | "title": "WaitFor Timeout",
259 | "description": "When using WaitFor, enter the number of seconds you wish to wait before Baseline considers remaining items a failure. This timer does not start until all other items are processed. Default if key is omitted is 600.",
260 | "property_order": 35
261 | },
262 | "Restart": {
263 | "type": "boolean",
264 | "title": "Force Restart",
265 | "default": true,
266 | "description": "This setting controls whether Baseline forces a restart after completion.",
267 | "property_order": 40
268 | },
269 | "LogOut": {
270 | "type": "boolean",
271 | "title": "Force Log Out",
272 | "default": false,
273 | "description": "This setting controls whether Baseline forces a restart after completion.",
274 | "property_order": 45
275 | },
276 | "BlurScreen": {
277 | "type": "boolean",
278 | "title": "Blur Screen",
279 | "default": true,
280 | "description": "This setting controls whether Baseline uses the SwiftDialog --blurscreen feature. Default is true. If you set to false, consider using --ontop in your DialogListOptions key",
281 | "property_order": 50
282 | },
283 | "ProgressBar": {
284 | "type": "boolean",
285 | "title": "Show Progress Bar",
286 | "default": false,
287 | "description": "This setting controls whether Baseline displays a progress bar.",
288 | "property_order": 55
289 | },
290 | "ProgressBarDisplayNames": {
291 | "type": "boolean",
292 | "title": "Show Display Names on Progress Bar",
293 | "default": false,
294 | "description": "This setting controls whether Baseline displays the current item being processed under the progress bar.",
295 | "property_order": 60
296 | },
297 | "CleanupAfterUse": {
298 | "type": "boolean",
299 | "title": "Cleanup After Use",
300 | "default": true,
301 | "description": "This setting controls whether Baseline deletes it's install directory after use: /usr/local/Baseline.",
302 | "property_order": 65
303 | },
304 | "ReinstallDialog": {
305 | "type": "boolean",
306 | "title": "Reinstall SwiftDialog",
307 | "description": "Deprecated: This feature was removed in v.2.3, as it is no longer needed. This setting controls whether Baseline will reinstall SwiftDialog as the final step before completion. Default behavior if this key is omitted will reinstall SwiftDialog if a custom PNG is found in /Library/Application Support/Dialog/Dialog.png. Leave this as default if you use Baseline to deploy a custom SwiftDialog icon. Set to false if you do not want Baseline to ever reinstall SwiftDialog. Set to True if you always want to reinstall SwiftDialog after each Baseline.",
308 | "property_order": 70
309 | },
310 | "InstallomatorSwiftDialogIntegration": {
311 | "type": "boolean",
312 | "title": "Use Installomator SwiftDialog Integration",
313 | "default": false,
314 | "description": "Tell Installomator to use it's built in SwiftDialog integration for the List View.",
315 | "property_order": 75
316 | },
317 | "IgnoreDefaultInstallomatorOptions": {
318 | "type": "boolean",
319 | "title": "Ignore Default Installomator Options",
320 | "default": false,
321 | "description": "By default, Baseline uses \"NOTIFY=silent\" and \"BLOCKING_PROCESS_ACTION=kill\" Installomator options. Setting this to True means Baseline will not use those options.",
322 | "property_order": 80
323 | },
324 | "Button1Enabled": {
325 | "type": "boolean",
326 | "title": "Button 1 Enabled",
327 | "default": false,
328 | "description": "This setting enables SwiftDialog \"Button 1\" (the OK button) on the Baseline List View. This allows users to dismiss the list view and continue working while Baseline runs. By default this button is disabled.",
329 | "property_order": 85
330 | },
331 | "JamfVerbose": {
332 | "type": "boolean",
333 | "title": "Verbose Jamf Items",
334 | "default": false,
335 | "description": "If set to true, when a Script is processing a jamf policy (/usr/local/bin/jamf), Baseline will watch the Jamf log to provide more verbose output on that line item. Similar to the Installomator integration, text and a progress circle will be used for all jamf items. ",
336 | "property_order": 90
337 | },
338 | "CloseListBeforeFinalScripts": {
339 | "type": "boolean",
340 | "title": "Close List View Before Final Scripts",
341 | "default": false,
342 | "description": "If set to true, Baseline will close the list view window before running FinalScripts. Use this option if you want to utilize FinalScripts to have the user complete actions prior to the Baseline completion dialog.",
343 | "property_order": 95
344 | },
345 | "DialogListOptions": {
346 | "type": "string",
347 | "title": "Dialog List Window Options",
348 | "description": "SwiftDialog options for the primary Baseline progress list window.",
349 | "property_order": 100
350 | },
351 | "DialogSuccessOptions": {
352 | "type": "string",
353 | "title": "Dialog Success Window Options",
354 | "description": "SwiftDialog options for the Success Dialog window.",
355 | "property_order": 105
356 | },
357 | "DialogFailureOptions": {
358 | "type": "string",
359 | "title": "Dialog Failure Window Options",
360 | "description": "SwiftDialog options for the Failure Dialog window.",
361 | "property_order": 110
362 | },
363 | "ExitCondition": {
364 | "type": "string",
365 | "title": "Exit Condition",
366 | "description": "Define a file or folder path. When Baseline starts, if this file or folder exists, Baseline will exit quietly without processing any items. Use this option if your management tool may re-send scripts when not desired.",
367 | "property_order": 115
368 | },
369 | "BailOutFile": {
370 | "type": "string",
371 | "title": "Bail Out File",
372 | "description": "Define a file path. If this file exists at any point while Baseline is running, Baseline will exit and will not process additional items.",
373 | "property_order": 120
374 | }
375 | }
376 | }
--------------------------------------------------------------------------------
/ProfileManifest/com.secondsonconsulting.baseline.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | pfm_app_url
6 | https://github.com/SecondSonConsulting/Baseline/releases
7 | pfm_description
8 | Baseline by Second Son Consulting Configuration
9 | pfm_documentation_url
10 | https://github.com/SecondSonConsulting/Baseline/wiki
11 | pfm_domain
12 | com.secondsonconsulting.baseline
13 | pfm_format_version
14 | 1
15 | pfm_last_modified
16 | 2025-04-23T00:00:00Z
17 | pfm_platforms
18 |
19 | macOS
20 |
21 | pfm_subkeys
22 |
23 |
24 | pfm_default
25 | Baseline configuration preferences
26 | pfm_description
27 | The human-readable description of this payload. This description appears on the Detail screen.
28 | pfm_description_reference
29 | Optional. A human-readable description of this payload. This description is shown on the Detail screen.
30 | pfm_name
31 | PayloadDescription
32 | pfm_title
33 | Payload Description
34 | pfm_type
35 | string
36 |
37 |
38 | pfm_default
39 | Baseline
40 | pfm_description
41 | The human-readable name for the profile payload. The name appears on the Detail screen and doesn't need to be unique.
42 | pfm_description_reference
43 | A human-readable name for the profile payload. This name is displayed on the Detail screen. It does not have to be unique.
44 | pfm_name
45 | PayloadDisplayName
46 | pfm_require
47 | always
48 | pfm_title
49 | Payload Display Name
50 | pfm_type
51 | string
52 |
53 |
54 | pfm_default
55 | com.secondsonconsulting.baseline
56 | pfm_description
57 | The reverse-DNS-style identifier for the payload. This identifier is usually the same as the TopLevel value, with an additional appended component. This string must be unique within the profile.
58 | During a profile replacement, the system updates payloads with the same 'PayloadIdentifier' and 'PayloadUUID' in the old and new profiles.
59 | pfm_description_reference
60 | A reverse-DNS-style identifier for the specific payload. It is usually the same identifier as the root-level PayloadIdentifier value with an additional component appended.
61 | pfm_name
62 | PayloadIdentifier
63 | pfm_require
64 | always
65 | pfm_title
66 | Payload Identifier
67 | pfm_type
68 | string
69 |
70 |
71 | pfm_default
72 | com.secondsonconsulting.baseline
73 | pfm_description
74 | The payload type, which each payload domain's reference page specifies.
75 | pfm_description_reference
76 | The payload type.
77 | pfm_name
78 | PayloadType
79 | pfm_require
80 | always
81 | pfm_title
82 | Payload Type
83 | pfm_type
84 | string
85 |
86 |
87 | pfm_description
88 | The globally unique identifier for the payload. The actual content is unimportant, but must be globally unique. In macOS, use 'uuidgen' to generate UUIDs.
89 | During a profile replacement, the system updates payloads with the same 'PayloadIdentifier' and 'PayloadUUID' in the old and new profiles.
90 | pfm_description_reference
91 | A globally unique identifier for the payload. The actual content is unimportant, but it must be globally unique. In macOS, you can use uuidgen to generate reasonable UUIDs.
92 | pfm_format
93 | ^[0-9A-Za-z]{8}-[0-9A-Za-z]{4}-[0-9A-Za-z]{4}-[0-9A-Za-z]{4}-[0-9A-Za-z]{12}$
94 | pfm_name
95 | PayloadUUID
96 | pfm_require
97 | always
98 | pfm_title
99 | Payload UUID
100 | pfm_type
101 | string
102 |
103 |
104 | pfm_default
105 | 1
106 | pfm_description
107 | The version of this specific payload.
108 | pfm_description_reference
109 | The version number of the individual payload.
110 | A profile can consist of payloads with different version numbers. For example, changes to the VPN software in iOS might introduce a new payload version to support additional features, but Mail payload versions would not necessarily change in the same release.
111 | pfm_name
112 | PayloadVersion
113 | pfm_range_list
114 |
115 | 1
116 |
117 | pfm_require
118 | always
119 | pfm_title
120 | Payload Version
121 | pfm_type
122 | integer
123 |
124 |
125 | pfm_description
126 | The human-readable string containing the name of the organization that provides the profile. This value doesn't need to match the organization payload value in the enclosing dictionary.
127 | pfm_name
128 | PayloadOrganization
129 | pfm_title
130 | Payload Organization
131 | pfm_type
132 | string
133 |
134 |
135 | pfm_description
136 | Define scripts that are run immediately as the user logs in, prior to any Baseline SwiftDialog windows. These scripts are the place to add your custom welcome messages or pre-flight scripts.
137 | pfm_name
138 | InitialScripts
139 | pfm_subkeys
140 |
141 |
142 | pfm_subkeys
143 |
144 |
145 | pfm_description
146 | The name you want to appear in SwiftDialog and logs. Initial Scripts will only be shown to the user if the item Fails.
147 | pfm_name
148 | DisplayName
149 | pfm_title
150 | Display Name
151 | pfm_type
152 | string
153 |
154 |
155 | pfm_description
156 | A path to the script you want to run. Can be a local file path or a URL.
157 | pfm_name
158 | ScriptPath
159 | pfm_title
160 | Script Path
161 | pfm_type
162 | string
163 |
164 |
165 | pfm_description
166 | The expected SHA256 of the script being run.
167 | pfm_name
168 | SHA256
169 | pfm_title
170 | SHA256
171 | pfm_type
172 | string
173 |
174 |
175 | pfm_description
176 | The expected MD5 of the script being run.
177 | pfm_name
178 | MD5
179 | pfm_title
180 | MD5
181 | pfm_type
182 | string
183 |
184 |
185 | pfm_description
186 | Arguments you want to pass to the script when it is run.
187 | pfm_name
188 | Arguments
189 | pfm_title
190 | Arguments
191 | pfm_type
192 | string
193 |
194 |
195 | pfm_description
196 | Enable this option to run the script as the logged in user.
197 | pfm_name
198 | AsUser
199 | pfm_title
200 | Run as User
201 | pfm_type
202 | boolean
203 |
204 |
205 | pfm_type
206 | dictionary
207 |
208 |
209 | pfm_title
210 | InitialScripts
211 | pfm_type
212 | array
213 |
214 |
215 | pfm_description
216 | Define Installomator labels to be run by Baseline. For information specific to Installomator, see the Installomator Github: https://github.com/Installomator/Installomator
217 | pfm_name
218 | Installomator
219 | pfm_subkeys
220 |
221 |
222 | pfm_subkeys
223 |
224 |
225 | pfm_description
226 | The name you want to appear in the SwiftDialog menu as this Installomator label is processed.
227 | pfm_name
228 | DisplayName
229 | pfm_title
230 | Display Name
231 | pfm_type
232 | string
233 |
234 |
235 | pfm_description
236 | The Installomator label.
237 | pfm_name
238 | Label
239 | pfm_title
240 | Label
241 | pfm_type
242 | string
243 |
244 |
245 | pfm_description
246 | Arguments you want to pass to the Installomator command for this label.
247 | pfm_name
248 | Arguments
249 | pfm_title
250 | Arguments
251 | pfm_type
252 | string
253 |
254 |
255 | pfm_description
256 | The icon you want to appear in the SwiftDialog menu as this Installomator label is processed. Can be a URL or local file path.
257 | pfm_name
258 | Icon
259 | pfm_title
260 | Icon
261 | pfm_type
262 | string
263 |
264 |
265 | pfm_app_min
266 | 2.1
267 | pfm_description
268 | The subtitle text to appear on this line item.
269 | pfm_name
270 | Subtitle
271 | pfm_title
272 | Subtitle
273 | pfm_type
274 | string
275 |
276 |
277 | pfm_type
278 | dictionary
279 |
280 |
281 | pfm_title
282 | Installomator Labels
283 | pfm_type
284 | array
285 |
286 |
287 | pfm_description
288 | Define Packages to be run by Baseline
289 | pfm_name
290 | Packages
291 | pfm_subkeys
292 |
293 |
294 | pfm_subkeys
295 |
296 |
297 | pfm_description
298 | The name you want to appear in the SwiftDialog menu as this package is installed.
299 | pfm_name
300 | DisplayName
301 | pfm_title
302 | Display Name
303 | pfm_type
304 | string
305 |
306 |
307 | pfm_description
308 | A path to the package you want to install. Can be a local file path or a URL.
309 | pfm_name
310 | PackagePath
311 | pfm_title
312 | Package Path
313 | pfm_type
314 | string
315 |
316 |
317 | pfm_description
318 | The expected TeamID of the package being installed.
319 | pfm_name
320 | TeamID
321 | pfm_title
322 | TeamID
323 | pfm_type
324 | string
325 |
326 |
327 | pfm_description
328 | The expected SHA256 of the package being installed.
329 | pfm_name
330 | SHA256
331 | pfm_title
332 | SHA256
333 | pfm_type
334 | string
335 |
336 |
337 | pfm_description
338 | The expected MD5 of the package being installed.
339 | pfm_name
340 | MD5
341 | pfm_title
342 | MD5
343 | pfm_type
344 | string
345 |
346 |
347 | pfm_description
348 | Arguments you want to pass to the installer command for this package.
349 | pfm_name
350 | Arguments
351 | pfm_title
352 | Arguments
353 | pfm_type
354 | string
355 |
356 |
357 | pfm_description
358 | The icon you want to appear in the SwiftDialog menu as this package is installed. Can be a URL or local file path.
359 | pfm_name
360 | Icon
361 | pfm_title
362 | Icon
363 | pfm_type
364 | string
365 |
366 |
367 | pfm_app_min
368 | 2.1
369 | pfm_description
370 | The subtitle text to appear on this line item.
371 | pfm_name
372 | Subtitle
373 | pfm_title
374 | Subtitle
375 | pfm_type
376 | string
377 |
378 |
379 | pfm_type
380 | dictionary
381 |
382 |
383 | pfm_title
384 | Packages
385 | pfm_type
386 | array
387 |
388 |
389 | pfm_description
390 | Define Scripts to be run by Baseline
391 | pfm_name
392 | Scripts
393 | pfm_subkeys
394 |
395 |
396 | pfm_subkeys
397 |
398 |
399 | pfm_description
400 | The name you want to appear in the SwiftDialog menu as this script is run.
401 | pfm_name
402 | DisplayName
403 | pfm_title
404 | Display Name
405 | pfm_type
406 | string
407 |
408 |
409 | pfm_description
410 | A path to the script you want to run. Can be a local file path or a URL.
411 | pfm_name
412 | ScriptPath
413 | pfm_title
414 | Script Path
415 | pfm_type
416 | string
417 |
418 |
419 | pfm_description
420 | The expected SHA256 of the script being run.
421 | pfm_name
422 | SHA256
423 | pfm_title
424 | SHA256
425 | pfm_type
426 | string
427 |
428 |
429 | pfm_description
430 | The expected MD5 of the script being run.
431 | pfm_name
432 | MD5
433 | pfm_title
434 | MD5
435 | pfm_type
436 | string
437 |
438 |
439 | pfm_description
440 | Arguments you want to pass to the script when it is run.
441 | pfm_name
442 | Arguments
443 | pfm_title
444 | Arguments
445 | pfm_type
446 | string
447 |
448 |
449 | pfm_description
450 | Enable this option to run the script as the logged in user.
451 | pfm_name
452 | AsUser
453 | pfm_title
454 | Run as User
455 | pfm_type
456 | boolean
457 |
458 |
459 | pfm_description
460 | The icon you want to appear in the SwiftDialog menu as this script is run. Can be a URL or local file path.
461 | pfm_name
462 | Icon
463 | pfm_title
464 | Icon
465 | pfm_type
466 | string
467 |
468 |
469 | pfm_app_min
470 | 2.1
471 | pfm_description
472 | The subtitle text to appear on this line item.
473 | pfm_name
474 | Subtitle
475 | pfm_title
476 | Subtitle
477 | pfm_type
478 | string
479 |
480 |
481 | pfm_type
482 | dictionary
483 |
484 |
485 | pfm_title
486 | Scripts
487 | pfm_type
488 | array
489 |
490 |
491 | pfm_app_min
492 | 2.2
493 | pfm_description
494 | Define files which you want Baseline to wait for. Use this for items not directly installed by Baseline, like VPP or MDM installed apps.
495 | pfm_name
496 | WaitFor
497 | pfm_subkeys
498 |
499 |
500 | pfm_subkeys
501 |
502 |
503 | pfm_description
504 | The name you want to appear in the SwiftDialog menu for this item we are waiting for.
505 | pfm_name
506 | DisplayName
507 | pfm_title
508 | Display Name
509 | pfm_type
510 | string
511 |
512 |
513 | pfm_description
514 | A path to the file you want Baseline to wait for.
515 | pfm_name
516 | Path
517 | pfm_title
518 | Path
519 | pfm_type
520 | string
521 |
522 |
523 | pfm_description
524 | The icon you want to appear in the SwiftDialog menu for this item. Can be a URL or local file path.
525 | pfm_name
526 | Icon
527 | pfm_title
528 | Icon
529 | pfm_type
530 | string
531 |
532 |
533 | pfm_app_min
534 | 2.1
535 | pfm_description
536 | The subtitle text to appear on this line item.
537 | pfm_name
538 | Subtitle
539 | pfm_title
540 | Subtitle
541 | pfm_type
542 | string
543 |
544 |
545 | pfm_type
546 | dictionary
547 |
548 |
549 | pfm_title
550 | WaitFor
551 | pfm_type
552 | array
553 |
554 |
555 | pfm_description
556 | Define scripts that are run just before completion dialogs. For reporting webhooks, cleanup tasks, etc. Similar to InitialScripts, these items do not appear on the List View.
557 | pfm_name
558 | FinalScripts
559 | pfm_subkeys
560 |
561 |
562 | pfm_subkeys
563 |
564 |
565 | pfm_description
566 | The name you want to appear SwiftDialog and logs. Final Scripts will only be shown to the user if the item Fails.
567 | pfm_name
568 | DisplayName
569 | pfm_title
570 | Display Name
571 | pfm_type
572 | string
573 |
574 |
575 | pfm_description
576 | A path to the script you want to run. Can be a local file path or a URL.
577 | pfm_name
578 | ScriptPath
579 | pfm_title
580 | Script Path
581 | pfm_type
582 | string
583 |
584 |
585 | pfm_description
586 | The expected SHA256 of the script being run.
587 | pfm_name
588 | SHA256
589 | pfm_title
590 | SHA256
591 | pfm_type
592 | string
593 |
594 |
595 | pfm_description
596 | The expected MD5 of the script being run.
597 | pfm_name
598 | MD5
599 | pfm_title
600 | MD5
601 | pfm_type
602 | string
603 |
604 |
605 | pfm_description
606 | Arguments you want to pass to the script when it is run.
607 | pfm_name
608 | Arguments
609 | pfm_title
610 | Arguments
611 | pfm_type
612 | string
613 |
614 |
615 | pfm_description
616 | Enable this option to run the script as the logged in user.
617 | pfm_name
618 | AsUser
619 | pfm_title
620 | Run as User
621 | pfm_type
622 | boolean
623 |
624 |
625 | pfm_type
626 | dictionary
627 |
628 |
629 | pfm_title
630 | FinalScripts
631 | pfm_type
632 | array
633 |
634 |
635 | pfm_app_min
636 | 2.2
637 | pfm_description
638 | When using WaitFor, enter the number of seconds you wish to wait before Baseline considers remaining items a failure. This timer does not start until all other items are processed. Default if key is omitted is 600.
639 | pfm_name
640 | WaitForTimeout
641 | pfm_title
642 | WaitFor Timeout
643 | pfm_type
644 | integer
645 |
646 |
647 | pfm_default
648 |
649 | pfm_description
650 | This setting controls whether Baseline forces a restart after completion.
651 | pfm_name
652 | Restart
653 | pfm_title
654 | Force Restart
655 | pfm_type
656 | boolean
657 |
658 |
659 | pfm_default
660 |
661 | pfm_description
662 | This setting controls whether Baseline forces a restart after completion.
663 | pfm_name
664 | LogOut
665 | pfm_title
666 | Force Log Out
667 | pfm_type
668 | boolean
669 |
670 |
671 | pfm_default
672 |
673 | pfm_description
674 | This setting controls whether Baseline uses the SwiftDialog --blurscreen feature. Default is true. If you set to false, consider using --ontop in your DialogListOptions key
675 | pfm_name
676 | BlurScreen
677 | pfm_title
678 | Blur Screen
679 | pfm_type
680 | boolean
681 |
682 |
683 | pfm_default
684 |
685 | pfm_description
686 | This setting controls whether Baseline displays a progress bar.
687 | pfm_name
688 | ProgressBar
689 | pfm_title
690 | Show Progress Bar
691 | pfm_type
692 | boolean
693 |
694 |
695 | pfm_default
696 |
697 | pfm_description
698 | This setting controls whether Baseline displays the current item being processed under the progress bar.
699 | pfm_name
700 | ProgressBarDisplayNames
701 | pfm_title
702 | Show Display Names on Progress Bar
703 | pfm_type
704 | boolean
705 |
706 |
707 | pfm_default
708 |
709 | pfm_description
710 | This setting controls whether Baseline deletes it's install directory after use: /usr/local/Baseline.
711 | pfm_name
712 | CleanupAfterUse
713 | pfm_title
714 | Cleanup After Use
715 | pfm_type
716 | boolean
717 |
718 |
719 | pfm_description
720 | Deprecated: This feature was removed in v.2.3, as it is no longer needed. This setting controls whether Baseline will reinstall SwiftDialog as the final step before completion. Default behavior if this key is omitted will reinstall SwiftDialog if a custom PNG is found in /Library/Application Support/Dialog/Dialog.png. Leave this as default if you use Baseline to deploy a custom SwiftDialog icon. Set to false if you do not want Baseline to ever reinstall SwiftDialog. Set to True if you always want to reinstall SwiftDialog after each Baseline.
721 | pfm_name
722 | ReinstallDialog
723 | pfm_title
724 | Reinstall SwiftDialog
725 | pfm_type
726 | boolean
727 |
728 |
729 | pfm_default
730 |
731 | pfm_description
732 | Tell Installomator to use it's built in SwiftDialog integration for the List View.
733 | pfm_name
734 | InstallomatorSwiftDialogIntegration
735 | pfm_title
736 | Use Installomator SwiftDialog Integration
737 | pfm_type
738 | boolean
739 |
740 |
741 | pfm_default
742 |
743 | pfm_description
744 | By default, Baseline uses "NOTIFY=silent" and "BLOCKING_PROCESS_ACTION=kill" Installomator options. Setting this to True means Baseline will not use those options.
745 | pfm_name
746 | IgnoreDefaultInstallomatorOptions
747 | pfm_title
748 | Ignore Default Installomator Options
749 | pfm_type
750 | boolean
751 |
752 |
753 | pfm_default
754 |
755 | pfm_description
756 | This setting enables SwiftDialog "Button 1" (the OK button) on the Baseline List View. This allows users to dismiss the list view and continue working while Baseline runs. By default this button is disabled.
757 | pfm_name
758 | Button1Enabled
759 | pfm_title
760 | Button 1 Enabled
761 | pfm_type
762 | boolean
763 |
764 |
765 | pfm_app_min
766 | 2.2
767 | pfm_default
768 |
769 | pfm_description
770 | If set to true, when a Script is processing a jamf policy (/usr/local/bin/jamf), Baseline will watch the Jamf log to provide more verbose output on that line item. Similar to the Installomator integration, text and a progress circle will be used for all jamf items.
771 | pfm_name
772 | JamfVerbose
773 | pfm_title
774 | Verbose Jamf Items
775 | pfm_type
776 | boolean
777 |
778 |
779 | pfm_app_min
780 | 2.3
781 | pfm_default
782 |
783 | pfm_description
784 | If set to true, Baseline will close the list view window before running FinalScripts. Use this option if you want to utilize FinalScripts to have the user complete actions prior to the Baseline completion dialog.
785 | pfm_name
786 | CloseListBeforeFinalScripts
787 | pfm_title
788 | Close List View Before Final Scripts
789 | pfm_type
790 | boolean
791 |
792 |
793 | pfm_description
794 | SwiftDialog options for the primary Baseline progress list window.
795 | pfm_name
796 | DialogListOptions
797 | pfm_title
798 | Dialog List Window Options
799 | pfm_type
800 | string
801 |
802 |
803 | pfm_description
804 | SwiftDialog options for the Success Dialog window.
805 | pfm_name
806 | DialogSuccessOptions
807 | pfm_title
808 | Dialog Success Window Options
809 | pfm_type
810 | string
811 |
812 |
813 | pfm_description
814 | SwiftDialog options for the Failure Dialog window.
815 | pfm_name
816 | DialogFailureOptions
817 | pfm_title
818 | Dialog Failure Window Options
819 | pfm_type
820 | string
821 |
822 |
823 | pfm_description
824 | Define a file or folder path. When Baseline starts, if this file or folder exists, Baseline will exit quietly without processing any items. Use this option if your management tool may re-send scripts when not desired.
825 | pfm_name
826 | ExitCondition
827 | pfm_title
828 | Exit Condition
829 | pfm_type
830 | string
831 |
832 |
833 | pfm_description
834 | Define a file path. If this file exists at any point while Baseline is running, Baseline will exit and will not process additional items.
835 | pfm_name
836 | BailOutFile
837 | pfm_title
838 | Bail Out File
839 | pfm_type
840 | string
841 |
842 |
843 | pfm_targets
844 |
845 | system
846 |
847 | pfm_title
848 | Baseline by Second Son Consulting
849 | pfm_unique
850 |
851 | pfm_version
852 | 5
853 |
854 |
855 |
--------------------------------------------------------------------------------
/Baseline.sh:
--------------------------------------------------------------------------------
1 | #!/bin/zsh --no-rcs
2 | set -x
3 | #dryRun=true
4 |
5 | # Written by Trevor Sysock of Second Son Consulting
6 | # @BigMacAdmin on the MacAdmins Slack
7 | # trevor@secondsonconsulting.com
8 |
9 | scriptVersion="2.3"
10 |
11 | # MIT License
12 | #
13 | # Copyright (c) 2024 Second Son Consulting
14 | #
15 | # Permission is hereby granted, free of charge, to any person obtaining a copy
16 | # of this software and associated documentation files (the "Software"), to deal
17 | # in the Software without restriction, including without limitation the rights
18 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
19 | # copies of the Software, and to permit persons to whom the Software is
20 | # furnished to do so, subject to the following conditions:
21 | #
22 | # The above copyright notice and this permission notice shall be included in all
23 | # copies or substantial portions of the Software.
24 | #
25 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
26 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
27 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
28 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
29 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
30 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
31 | # SOFTWARE.
32 | #
33 |
34 | ########################################################################################################
35 | ########################################################################################################
36 | ##
37 | ## DEFINE INITIAL VARIABLES
38 | ##
39 | ########################################################################################################
40 | ########################################################################################################
41 |
42 | if [ "${1}" = '--version' ]; then
43 | echo "$(basename $0) by Second Son Consulting - v. $scriptVersion"
44 | exit 0
45 | fi
46 |
47 | #################################
48 | # Declare file/folder paths #
49 | #################################
50 | #Baseline files/folders
51 | BaselineConfig="/Library/Managed Preferences/com.secondsonconsulting.baseline.plist"
52 | BaselineDir="/usr/local/Baseline"
53 | BaselineTempDir="$(mktemp -d /var/tmp/baselineTempDir.XXXXXXX)"
54 | customConfigPlist="$BaselineDir/BaselineConfig.plist"
55 | logFile="/var/log/Baseline.log"
56 | reportFile="/var/log/Baseline-Report.txt"
57 | BaselinePath="$BaselineDir/Baseline.sh"
58 | BaselineScripts="$BaselineDir/Scripts"
59 | BaselinePackages="$BaselineDir/Packages"
60 | BaselineIcons="$BaselineDir/Icons"
61 | BaselineLaunchDaemon="/Library/LaunchDaemons/com.secondsonconsulting.baseline.plist"
62 | BaselineTempIconsDir=$(mktemp -d "${BaselineTempDir}/baselineTmpIcons.XXXX")
63 | ScriptOutputLog="/var/log/Baseline-ScriptsOutput.log"
64 |
65 | #Binaries
66 | pBuddy="/usr/libexec/PlistBuddy"
67 | dialogPath="/usr/local/bin/dialog"
68 | dialogAppPath="/Library/Application Support/Dialog/Dialog.app"
69 | installomatorPath="/usr/local/Installomator/Installomator.sh"
70 |
71 | #Other stuff
72 | dialogCommandFile=$(mktemp "${BaselineTempDir}/baselineDialog.XXXXXX")
73 | dialogJsonFile=$(mktemp "${BaselineTempDir}/baselineJson.XXXX")
74 | expectedDialogTeamID="PWA5E9TQ59"
75 | defaultWaitForTimeout=600
76 |
77 | # Path to the Jamf log file
78 | jamfLogFile="/private/var/log/jamf.log"
79 |
80 | chmod -R 655 "${BaselineTempDir}"
81 |
82 | ########################################################################################################
83 | ########################################################################################################
84 | ##
85 | ## DEFINE FUNCTIONS
86 | ##
87 | ########################################################################################################
88 | ########################################################################################################
89 |
90 | #################################
91 | # Logging and Housekeeping #
92 | #################################
93 |
94 | function check_root(){
95 |
96 | # check we are running as root
97 | if [[ $(id -u) -ne 0 ]]; then
98 | echo "ERROR: This script must be run as root **EXITING**"
99 | # Delete Baseline Temp Dir
100 | rm_if_exists "${BaselineTempDir}"
101 | exit 1
102 | fi
103 | }
104 |
105 | function make_directory(){
106 | if [ ! -d "${1}" ]; then
107 | debug_message "Folder does not exist. Making it: ${1}"
108 | mkdir -p "${1}"
109 | fi
110 | }
111 |
112 | #Used only for debugging. Gives feedback into standard out if verboseMode=1, also to $logFile if you set it
113 | function debug_message(){
114 | if [ "$verboseMode" = 1 ]; then
115 | /bin/echo "DEBUG: $*"
116 | fi
117 | }
118 |
119 | #Publish a message to the log (and also to the debug channel)
120 | function log_message(){
121 | echo "$(date): $*" | tee >( cat >> "$logFile" )
122 | debug_message "$*"
123 | }
124 |
125 | function rotate_logs(){
126 | touch "$logFile"
127 | chmod 655 "$logFile"
128 | if [ "$(wc -l < "$logFile" | xargs)" -ge 30000 ]; then
129 | log_message "Rotating Logs"
130 | mv "$logFile" "$logFile".old
131 | touch "$logFile"
132 | log_message "Logfile Rotated"
133 | fi
134 | }
135 |
136 | #Report messages go to our report, but also pass through log_message (and thus, also to debug_message)
137 | function report_message(){
138 | /bin/echo "$@" >> "$reportFile"
139 | log_message "$@"
140 | }
141 |
142 | # Initiate logging
143 | function initiate_logging(){
144 | if ! touch "$logFile" ; then
145 | debug_message "ERROR: Logging fail. Cannot create log file"
146 | # Delete Baseline Temp Dir
147 | rm_if_exists "${BaselineTempDir}"
148 | exit 1
149 | else
150 | log_message "Baseline.sh initiated"
151 | fi
152 | }
153 |
154 | #Only delete something if the variable has a value!
155 | function rm_if_exists(){
156 | if [ -n "${1}" ] && [ -e "${1}" ];then
157 | /bin/rm -rf "${1}"
158 | fi
159 | }
160 |
161 | function initiate_report(){
162 | if ! touch "$reportFile" ; then
163 | debug_message "ERROR: Reporting fail. Cannot create report file"
164 | # Delete Baseline Temp Dir
165 | rm_if_exists "${BaselineTempDir}"
166 | exit 1
167 | else
168 | rm_if_exists "$reportFile"
169 | report_message "Report created: $(date)"
170 | fi
171 | }
172 |
173 | #Define our script exit process. Usage: cleanup_and_exit 'exitcode' 'exit message'
174 | function cleanup_and_exit(){
175 | # Check if we are going to restart
176 | check_restart_option
177 |
178 | # Check if we are leaving the Baseline working directory or deleting
179 | cleanupAfterUse=$($pBuddy -c "Print :CleanupAfterUse" "$BaselineConfig" 2> /dev/null)
180 |
181 | if [[ $cleanupAfterUse == "false" ]]; then
182 | cleanupBaselineDirectory="false"
183 | else
184 | cleanupBaselineDirectory="true"
185 | fi
186 |
187 | # Log message
188 | report_message "$2"
189 | report_message "Baseline exited with error code: $1"
190 |
191 | # Delete the Baseline LaunchDaemon
192 | # Doing this in a loop because I've seen edge cases where it failed unexpectedly and it is high impact.
193 | while [ -e "$BaselineLaunchDaemon" ]; do
194 | rm_if_exists "$BaselineLaunchDaemon"
195 | sleep 1
196 | done
197 |
198 | kill "$caffeinatepid"
199 | dialog_command "quit:"
200 | rm_if_exists "${BaselineTempDir}"
201 | if [ "$dryRun" != true ] && [ "$cleanupBaselineDirectory" = "true" ] ; then
202 | rm_if_exists "$BaselineDir"
203 | fi
204 | # Delete Baseline Temp Dir
205 | rm_if_exists "${BaselineTempDir}"
206 | exit "$1"
207 | }
208 |
209 | # This function doesn't always shut down, but I'm leaving the name in place for now at least.
210 | # Usage: cleanup_and_exit 'exitcode' 'exit message'
211 | function cleanup_and_restart(){
212 | # Check if we are going to restart
213 | check_restart_option
214 |
215 | # Check if we are leaving the Baseline working directory or deleting
216 | cleanupAfterUse=$($pBuddy -c "Print :CleanupAfterUse" "$BaselineConfig" 2> /dev/null )
217 |
218 | if [ "$cleanupAfterUse" = "false" ]; then
219 | cleanupBaselineDirectory="false"
220 | else
221 | cleanupBaselineDirectory="true"
222 | fi
223 |
224 | # Log message
225 | report_message "$2"
226 |
227 | # Delete the LaunchDaemon. Saw an edge case where it didn't delete once, so I made it a while loop.
228 | while [ -e "$BaselineLaunchDaemon" ]; do
229 | rm_if_exists "$BaselineLaunchDaemon"
230 | sleep 1
231 | done
232 | # Kill our caffeinate command
233 | kill "$caffeinatepid"
234 | # Close dialog window
235 | dialog_command "quit:"
236 |
237 |
238 | # Check if we are deleting the Baseline working directory and do it
239 | if [ $cleanupBaselineDirectory = "true" ]; then
240 | rm_if_exists "$BaselineDir"
241 | fi
242 |
243 | # Determine exit configuration
244 | # If ForceRestart is set to false, and dry run is off
245 | if $forceRestart && ! $dryRun ; then
246 | report_message "Force Restart is configured. Restarting"
247 | # Delete Baseline Temp Dir
248 | rm_if_exists "${BaselineTempDir}"
249 | log_message "Forcing restart"
250 | shutdown -r now
251 | # If Force Log Out is set to true, and dry run is off
252 | elif $forceLogOut && ! $dryRun; then
253 | report_message "Force Log Out is set to true."
254 | osascript -e "tell application \"/System/Library/CoreServices/loginwindow.app\" to «event aevtrlgo»"
255 | # Delete Baseline Temp Dir
256 | rm_if_exists "${BaselineTempDir}"
257 | exit "$1"
258 | elif ! $forceLogOut && ! $forceRestart && ! $dryRun; then
259 | report_message "Force Log Out and Force Restart are false. Exiting with no action."
260 | # Delete Baseline Temp Dir
261 | rm_if_exists "${BaselineTempDir}"
262 | exit "$1"
263 | # If the script is in DryRun mode
264 | elif $dryRun; then
265 | report_message "Dry Run Enabled, no exit action taken."
266 | report_message "ForceRestart is set to: $forceRestart"
267 | report_message "ForceLogOut is set to: $forceLogOut"
268 | # Delete Baseline Temp Dir
269 | rm_if_exists "${BaselineTempDir}"
270 | exit "$1"
271 | fi
272 |
273 | # Shutting down
274 | log_message "Unknown ExitAction determined. Falling back on default to ForceRestart"
275 | # Delete Baseline Temp Dir
276 | rm_if_exists "${BaselineTempDir}"
277 | shutdown -r now
278 | }
279 |
280 | function no_sleeping(){
281 |
282 | /usr/bin/caffeinate -d -i -m -u &
283 | caffeinatepid=$!
284 |
285 | }
286 |
287 | # execute a dialog command
288 | function dialog_command(){
289 | /bin/echo "$@" >> $dialogCommandFile
290 | sleep .1
291 | }
292 |
293 | #This function is modified from the awesome one given to us via Adam Codega. Thanks Adam!
294 | #https://github.com/acodega/dialog-scripts/blob/main/dialogCheckFunction.sh
295 |
296 | function install_dialog(){
297 |
298 | # Check for Dialog and install if not found. We'll try 10 times before exiting the script with a fail.
299 | dialogInstallAttempts=0
300 | while [ ! -e "$dialogAppPath" ] && [ "$dialogInstallAttempts" -lt 10 ]; do
301 | # If SwiftDialog.pkg exists in the Packages folder check the TeamID is valid, and install it
302 | localDialogTeamID=$(/usr/sbin/spctl -a -vv -t install "$BaselinePackages/SwiftDialog.pkg" 2>&1 | awk '/origin=/ {print $NF }' | tr -d '()')
303 | if [ "$expectedDialogTeamID" = "$localDialogTeamID" ]; then
304 | /usr/sbin/installer -pkg "$BaselinePackages/SwiftDialog.pkg" -target / > /dev/null 2>&1
305 | # If Installomator is already here use that
306 | elif [ -e "$installomatorPath" ]; then
307 | "$installomatorPath" swiftdialog INSTALL=force NOTIFY=silent BLOCKING_PROCESS_ACTION=ignore > /dev/null 2>&1
308 | dialogInstallAttempts=$((dialogInstallAttempts+1))
309 | else
310 | # Get the URL of the latest PKG From the Dialog GitHub repo
311 | # Expected Team ID of the downloaded PKG
312 | dialogURL=$(curl -L --silent --fail "https://api.github.com/repos/swiftDialog/swiftDialog/releases/latest" | awk -F '"' "/browser_download_url/ && /pkg\"/ { print \$4; exit }")
313 | log_message "Dialog not found. Installing."
314 | # Create temporary working directory
315 | workDirectory=$( /usr/bin/basename "$0" )
316 | tempDirectory=$( /usr/bin/mktemp -d "/private/tmp/$workDirectory.XXXXXX" )
317 | # Download the installer package
318 | /usr/bin/curl --location --silent "$dialogURL" -o "$tempDirectory/SwiftDialog.pkg"
319 | # Verify the download
320 | teamID=$(/usr/sbin/spctl -a -vv -t install "$tempDirectory/SwiftDialog.pkg" 2>&1 | awk '/origin=/ {print $NF }' | tr -d '()')
321 | # Install the package if Team ID validates
322 | if [ "$expectedDialogTeamID" = "$teamID" ]; then
323 | /usr/sbin/installer -pkg "$tempDirectory/SwiftDialog.pkg" -target / > /dev/null 2>&1
324 | fi
325 | #If Dialog wasn't installed, wait 5 seconds and increase the attempt count
326 | if [ ! -e "$dialogAppPath" ]; then
327 | log_message "Dialog installation failed."
328 | sleep 5
329 | dialogInstallAttempts=$((dialogInstallAttempts+1))
330 | fi
331 | # Remove the temporary working directory when done
332 | rm_if_exists "$tempDirectory"
333 | fi
334 | done
335 | }
336 |
337 | function install_installomator(){
338 |
339 | # Check for Installomator and install if not found. We'll try 10 times before exiting the script with a fail.
340 | installomatorInstallAttempts=0
341 | while [ ! -e "$installomatorPath" ] && [ "$installomatorInstallAttempts" -lt 10 ]; do
342 | # Check if there is a local Installomator.pkg, and if so run it.
343 | if [ -e "${BaselinePackages}/Installomator.pkg" ]; then
344 | /usr/sbin/installer -pkg "${BaselinePackages}/Installomator.pkg" -target / > /dev/null 2>&1
345 | else
346 | # Get the URL of the latest PKG From the Installomator GitHub repo
347 | # Expected Team ID of the downloaded PKG
348 | installomatorURL=$(curl --silent -L --fail "https://api.github.com/repos/Installomator/Installomator/releases/latest" | awk -F '"' "/browser_download_url/ && /pkg\"/ { print \$4; exit }")
349 | expectedTeamID="JME5BW3F3R"
350 | log_message "Installomator not found. Installing."
351 | # Create temporary working directory
352 | workDirectory=$( /usr/bin/basename "$0" )
353 | tempDirectory=$( /usr/bin/mktemp -d "/private/tmp/$workDirectory.XXXXXX" )
354 | # Download the installer package
355 | /usr/bin/curl --location --silent "$installomatorURL" -o "$tempDirectory/Installomator.pkg"
356 | # Verify the download
357 | teamID=$(/usr/sbin/spctl -a -vv -t install "$tempDirectory/Installomator.pkg" 2>&1 | awk '/origin=/ {print $NF }' | tr -d '()')
358 | # Install the package if Team ID validates
359 | if [ "$expectedTeamID" = "$teamID" ]; then
360 | /usr/sbin/installer -pkg "$tempDirectory/Installomator.pkg" -target / > /dev/null 2>&1
361 | installomatorInstallExitCode=$?
362 | fi
363 | if [ ! -e "$installomatorPath" ]; then
364 | log_message "Installomator installation failed."
365 | sleep 5
366 | installomatorInstallAttempts=$((installomatorInstallAttempts+1))
367 | fi
368 | # Remove the temporary working directory when done
369 | rm_if_exists "$tempDirectory"
370 | fi
371 | done
372 | }
373 |
374 | #Checks if a user is logged in yet, and if not it waits and loops until we can confirm there is a real user
375 | function wait_for_user(){
376 | #Set our test to false
377 | verifiedUser="false"
378 |
379 | #Loop until user is found
380 | while [ "$verifiedUser" = "false" ]; do
381 | #Get currently logged in user
382 | currentUser=$( echo "show State:/Users/ConsoleUser" | scutil | awk '/Name :/ { print $3 }' )
383 | #Verify the current user is not root, loginwindow, or _mbsetupuser
384 | if [ "$currentUser" = "root" ] \
385 | || [ "$currentUser" = "loginwindow" ] \
386 | || [ "$currentUser" = "_mbsetupuser" ] \
387 | || [ -z "$currentUser" ]
388 | then
389 | #If we aren't verified yet, wait 1 second and try again
390 | sleep 1
391 | else
392 | #Logged in user found, but continue the loop until Dock and Finder processes are running
393 | if pgrep -q "dock" && pgrep -q "Finder"; then
394 | uid=$(id -u "$currentUser")
395 | log_message "Verified User is logged in: $currentUser UID: $uid"
396 | verifiedUser="true"
397 | fi
398 | fi
399 | debug_message "Disabling verbose output to prevent logspam while waiting for user at timestamp: $(date +%s)"
400 | set +x
401 | done
402 | debug_message "Re-enabling verbose output after finding user at timestamp: $(date +%s)"
403 |
404 | }
405 |
406 | #Check for custom config. We prioritize this even over a mobileconfig file.
407 | function check_for_custom_plist(){
408 | if [ -e $customConfigPlist ] && ! $configFromArgument; then
409 | BaselineConfig="$customConfigPlist"
410 | fi
411 | }
412 |
413 | #Verify configuration file
414 | function verify_configuration_file(){
415 | #We need to make sure our configuration file is in place. By the time the user logs in, this should have happened.
416 | debug_message "Verifying configuration file. Failure here probably means an MDM profile hasn't been properly scoped, or there's a problem with the MDM delivering the profile."
417 |
418 | #Set timeout variables
419 | configFileTimeout=600
420 | configFileWaiting=0
421 |
422 | # Look for a custom plist
423 | check_for_custom_plist
424 |
425 | # While the plist or configuration file doesn't exist, wait and timeout at 10 minutes if never found.
426 | while [ ! -e $BaselineConfig ]; do
427 | check_for_custom_plist
428 | #wait 2 seconds
429 | sleep 2
430 | debug_message "Configuration file not found"
431 | configFileWaiting=$((configFileWaiting+2))
432 | if [ $configFileWaiting -gt $configFileTimeout ]; then
433 | cleanup_and_exit 1 "ERROR: Configuration file not found within $configFileTimeout seconds. Exiting."
434 | fi
435 | done
436 | debug_message "Configuration file found successfully: $BaselineConfig "
437 |
438 | # If we're working off an MDM configuration profile, copy it to our temp location and go off the copy.
439 | # Have seen edge cases where an MDM removes or re-applies profiles, this will prevent that from causing issues.
440 | if [[ "$BaselineConfig" == "/Library/Managed Preferences/com.secondsonconsulting.baseline.plist" ]]; then
441 | cp "$BaselineConfig" "$BaselineTempDir/BaselineConfig.plist"
442 | BaselineConfig="$BaselineTempDir/BaselineConfig.plist"
443 | fi
444 | }
445 |
446 | function build_installomator_array(){
447 | #Set an index internal to this function
448 | index=0
449 | #Loop through and test if there is a value in the slot of this index for the given array
450 | #If this command fails it means we've reached the end of the array in the config file and we exit our loop
451 |
452 | while $pBuddy -c "Print :Installomator:${index}" "$BaselineConfig" > /dev/null 2>&1; do
453 | #Get the Display Name of the current item
454 | currentDisplayName=$($pBuddy -c "Print :Installomator:${index}:DisplayName" "$BaselineConfig")
455 | dialogList+="$currentDisplayName"
456 | #Done looping. Increase our array value and loop again.
457 | index=$((index+1))
458 | done
459 | }
460 |
461 | function process_installomator_labels(){
462 | #Set an index internal to this function
463 | currentIndex=0
464 | #Loop through and test if there is a value in the slot of this index for the given array
465 | #If this command fails it means we've reached the end of the array in the config file (or there are none) and we exit our loop
466 | while $pBuddy -c "Print :Installomator:${currentIndex}" "$BaselineConfig" > /dev/null 2>&1; do
467 | check_for_bail_out
468 | if [ ! -e "$installomatorPath" ]; then
469 | cleanup_and_exit 1 "ERROR: Installomator failed to install after numerous attempts. Exiting."
470 | fi
471 | #Set the current label name
472 | currentLabel=$($pBuddy -c "Print :Installomator:${currentIndex}:Label" "$BaselineConfig")
473 | #Check if there are Options defined, and set the variable accordingly
474 | if $pBuddy -c "Print :Installomator:${currentIndex}:Arguments" "$BaselineConfig" > /dev/null 2>&1; then
475 | #This label has options defined
476 | currentArguments=$($pBuddy -c "Print :Installomator:${currentIndex}:Arguments" "$BaselineConfig")
477 | else
478 | #This label does not have options defined
479 | currentArguments=""
480 | fi
481 | #Now we have to do a trick in case there are multiple arguments, some of which are quoted together
482 | #Consider: /path/to/script.sh --font "Times New Roman"
483 | #Used the eval trick outlined here: https://superuser.com/questions/1066455/how-to-split-a-string-with-quotes-like-command-arguments-in-bash
484 | currentArgumentArray=()
485 | if [ -n "$currentArguments" ]; then
486 | eval 'for argument in '$currentArguments'; do currentArgumentArray+=$argument; done'
487 | fi
488 | #Get the display name of the label we're installing. We need this to update the dialog list
489 | currentDisplayName=$($pBuddy -c "Print :Installomator:${currentIndex}:DisplayName" "$BaselineConfig")
490 |
491 | # Configure Installomator SwiftDialog Integration
492 | useInstallomatorSwiftDialogIntegration=$($pBuddy -c "Print :InstallomatorSwiftDialogIntegration" "$BaselineConfig" 2> /dev/null)
493 |
494 | # If we're using the integrated SwiftDialog, then
495 | if [[ $useInstallomatorSwiftDialogIntegration == "true" ]]; then
496 | currentArgumentArray+="DIALOG_CMD_FILE=\"$dialogCommandFile\""
497 | currentArgumentArray+=DIALOG_LIST_ITEM_NAME=\"$currentDisplayName\"
498 | else
499 | #Update the dialog window so that this item shows as "pending"
500 | dialog_command "listitem: title: $currentDisplayName, status: wait"
501 | fi
502 |
503 | set_progressbar_text "$currentDisplayName"
504 | #Call installomator with our desired options. Default options first, so that they can be overriden by "currentArguments"
505 | $installomatorPath $currentLabel ${defaultInstallomatorOptions[@]} ${currentArgumentArray[@]} > /dev/null 2>&1
506 | installomatorExitCode=$?
507 | if [ $installomatorExitCode != 0 ]; then
508 | report_message "Failed Item - Installomator: $currentLabel - Exit Code: $installomatorExitCode"
509 | failList+=("$currentDisplayName")
510 | # If we're NOT using the integrated SwiftDialog, then
511 | if [[ $useInstallomatorSwiftDialogIntegration != "true" ]]; then
512 | dialog_command "listitem: title: $currentDisplayName, status: fail"
513 | fi
514 | else
515 | report_message "Successful Item - Installomator: $currentLabel"
516 | successList+=("$currentDisplayName")
517 | if [[ $useInstallomatorSwiftDialogIntegration != "true" ]]; then
518 | dialog_command "listitem: title: $currentDisplayName, status: success"
519 | fi
520 | fi
521 | update_tracker "$currentDisplayName" $installomatorExitCode
522 | currentIndex=$((currentIndex+1))
523 | # This gets set for use with the BailOut feature
524 | previousDisplayName="$currentDisplayName"
525 | check_for_bail_out
526 | increment_progress_bar
527 | done
528 | }
529 |
530 | # Our main list builder for the Dialog window
531 | function build_dialog_array(){
532 | ## Usage: Build the dialog array for the given profile configuration key. $1 is the name of the key
533 | ## Example: build_dialog_array Scripts | InitialScripts | Packages | Installomator
534 |
535 | # Set the MDM key to the given argument
536 | configKey="${1}"
537 |
538 | #Set an index internal to this function
539 | index=0
540 | #Loop through and test if there is a value in the slot of this index for the given array
541 | #If this command fails it means we've reached the end of the array in the config file and we exit our loop
542 |
543 | while $pBuddy -c "Print :$configKey:${index}" "$BaselineConfig" > /dev/null 2>&1; do
544 | #Get the Display Name of the current item
545 | currentDisplayName=$($pBuddy -c "Print :$configKey:${index}:DisplayName" "$BaselineConfig")
546 | dialogList+="$currentDisplayName"
547 |
548 | #Get the icon path if populated in the configuration profile
549 | if $pBuddy -c "Print :$configKey:${index}:Icon" "$BaselineConfig" > /dev/null 2>&1; then
550 | currentIconPath=$($pBuddy -c "Print :$configKey:${index}:Icon" "$BaselineConfig")
551 | # Check if Icon is remotely hosted URL
552 | if [[ ${currentIconPath:0:4} == "http" ]]; then
553 | log_message "Icon set to URL: $currentIconPath"
554 | # Check if Icon is an SF Symbol
555 | elif [[ ${currentIconPath:0:3} == 'SF=' ]]; then
556 | log_message "Icon set to SF Symbol: $currentIconPath"
557 | #Check of the given icon path exists on disk
558 | elif [ -e "$currentIconPath" ]; then
559 | log_message "Icon found: $currentIconPath"
560 | elif [ -e "$BaselineTempIconsDir/$currentIconPath" ]; then
561 | log_message "Icon found: $currentIconPath"
562 | currentIconPath="$BaselineTempIconsDir/$currentIconPath"
563 | chmod 655 "${currentIconPath}"
564 | else
565 | #If we can't find the local file, report and leave blank
566 | log_message "ERROR: Icon key cannot be located: $currentIconPath"
567 | currentIconPath=""
568 | fi
569 | else
570 | #If no icon key is set, ensure it's blank
571 | currentIconPath=""
572 | fi
573 |
574 | #Get the desired subtitle if populated in the configuration profile
575 | if $pBuddy -c "Print :$configKey:${index}:Subtitle" "$BaselineConfig" > /dev/null 2>&1; then
576 | currentSubtitle=$($pBuddy -c "Print :$configKey:${index}:Subtitle" "$BaselineConfig")
577 | else
578 | #If no icon key is set, ensure it's blank
579 | currentSubtitle=""
580 | fi
581 |
582 | #Generate JSON entry for item
583 | #NOTE: We will strip out the final comma later to ensure a valid JSON
584 | dialogListJson+="{\"title\" : \"$currentDisplayName\",\"subtitle\" : \"$currentSubtitle\", \"icon\" : \"$currentIconPath\", \"status\" : \"\"},"
585 |
586 | #Done looping. Increase our array value and loop again.
587 | index=$((index+1))
588 | progressBarTotal=$((progressBarTotal+1))
589 | done
590 | }
591 |
592 | function process_scripts(){
593 | # Usage: process_scripts ProfileKey
594 | # Actual use: process_scripts [ InitialScripts | Scripts ]
595 | #Set an index internal to this function
596 | currentIndex=0
597 | #Loop through and test if there is a value in the slot of this index for the given array
598 | #If this command fails it means we've reached the end of the array in the config file (or there are none) and we exit our loop
599 | while $pBuddy -c "Print :${1}:${currentIndex}" "$BaselineConfig" > /dev/null 2>&1; do
600 | check_for_bail_out
601 | #Unset variables for next loop
602 | unset useVerboseJamf
603 | unset jamfVerbosePID
604 | unset expectedMD5
605 | unset actualMD5
606 | unset expectedSHA256
607 | unset actualSHA256
608 | unset currentArguments
609 | unset currentArgumentArray
610 | unset currentScript
611 | unset currentScriptPath
612 | unset currentDisplayName
613 | unset scriptDownloadExitCode
614 | #Get the display name of the label we're installing. We need this to update the dialog list
615 | currentDisplayName=$($pBuddy -c "Print :${1}:${currentIndex}:DisplayName" "$BaselineConfig")
616 | #Set the current script name
617 | currentScriptPath=$($pBuddy -c "Print :${1}:${currentIndex}:ScriptPath" "$BaselineConfig")
618 | #Set where we are running in the user context or root
619 | asUser=$($pBuddy -c "Print :${1}:${currentIndex}:AsUser" "$BaselineConfig" 2> /dev/null)
620 | #Check if the defined script is a remote path
621 | if [[ ${currentScriptPath:0:4} == "http" ]]; then
622 | #Set variable to the base file name to be downloaded
623 | currentScript="$BaselineScripts/"$(basename "$currentScriptPath")
624 | #Download the remote script, and put it in the Baseline Scripts directory
625 | curl -s --fail-with-body "${currentScriptPath}" -o "$currentScript"
626 | #Capture the exit code of our curl command
627 | scriptDownloadExitCode=$?
628 | #Check if curl exited cleanly
629 | if [ "$scriptDownloadExitCode" != 0 ];then
630 | #Report a failed download
631 | report_message "Failed Item - Script download error: $currentScriptPath"
632 | #Rm the output of our curl command. This will result in it being processed as a failure
633 | rm_if_exists "$currentScript"
634 | else
635 | log_message "Script downloaded successfully: $currentScriptPath"
636 | #Make our downloaded script executable
637 | chmod +x "$currentScript"
638 | fi
639 | #Check if the given script exists on disk
640 | elif [ -e "$currentScriptPath" ]; then
641 | # The path to the script is a local file path which exists
642 | currentScript="$currentScriptPath"
643 | elif [ -e "$BaselineScripts/$currentScriptPath" ]; then
644 | currentScript="$BaselineScripts/$currentScriptPath"
645 | fi
646 | #If the currentScript variable still isn't set to an existing file we need to bail..
647 | if [ ! -e "$currentScript" ]; then
648 | report_message "Failed Item - Script does not exist: $currentScript"
649 | # Iterate the index up one
650 | currentIndex=$((currentIndex+1))
651 | increment_progress_bar
652 | # Report the fail
653 | dialog_command "listitem: title: $currentDisplayName, status: fail"
654 | failList+=("$currentDisplayName")
655 | update_tracker $currentDisplayName 99
656 | # Bail this pass through the while loop and continue processing next item
657 | continue
658 | fi
659 | #Check if this is a Jamf binary call and if we're using verbose jamf output
660 | if [[ ${currentScriptPath} == "/usr/local/bin/jamf" ]] && $showVerboseJamf ; then
661 | jamf_verbose_dialog "$currentDisplayName" & jamfVerbosePID=$!
662 | fi
663 | ##Check for SHA256 validation
664 | if $pBuddy -c "Print :${1}:${currentIndex}:SHA256" "$BaselineConfig" > /dev/null 2>&1; then
665 | #This script has SHA256 validation provided
666 | #Read the expected SHA256 value from the profile
667 | expectedSHA256=$($pBuddy -c "Print :${1}:${currentIndex}:SHA256" "$BaselineConfig")
668 | #Calculate the actual SHA256 of the script
669 | actualSHA256=$(shasum -a 256 "$currentScript" | awk '{ print $1 }')
670 | #Evaluate whether the expected and actual SHA256 do not match
671 | if [ "$actualSHA256" != "$expectedSHA256" ]; then
672 | report_message "Failed Item - Script SHA256 error: $currentScriptPath - Expected $expectedSHA256 - Actual $actualSHA256"
673 | # Iterate the index up one
674 | currentIndex=$((currentIndex+1))
675 | # Only increment the progress bar if we're processing Scripts, not InitialScripts since users won't see those
676 | if [ "$1" = "Scripts" ]; then
677 | increment_progress_bar
678 | fi
679 | # Report the fail
680 | dialog_command "listitem: title: $currentDisplayName, status: fail"
681 | failList+=("$currentDisplayName")
682 | # Bail this pass through the while loop and continue processing next item
683 | continue
684 | fi
685 | fi
686 | ##Check for MD5 validation
687 | if $pBuddy -c "Print :${1}:${currentIndex}:MD5" "$BaselineConfig" > /dev/null 2>&1; then
688 | #This script has MD5 validation provided
689 | #Read the expected MD5 value from the profile
690 | expectedMD5=$($pBuddy -c "Print :${1}:${currentIndex}:MD5" "$BaselineConfig")
691 | #Calculate the actual MD5 of the script
692 | actualMD5=$(md5 -q "$currentScript")
693 | #Evaluate whether the expected and actual MD5 do not match
694 | if [ "$actualMD5" != "$expectedMD5" ]; then
695 | report_message "Failed Item - Script MD5 error: $currentScriptPath - Expected $expectedMD5 - Actual $actualMD5"
696 | # Iterate the index up one
697 | currentIndex=$((currentIndex+1))
698 | # Only increment the progress bar if we're processing Scripts, not InitialScripts since users won't see those
699 | if [ "$1" = "Scripts" ]; then
700 | increment_progress_bar
701 | fi
702 | # Report the fail
703 | dialog_command "listitem: title: $currentDisplayName, status: fail"
704 | failList+=("$currentDisplayName")
705 | # Bail this pass through the while loop and continue processing next item
706 | continue
707 | fi
708 | fi
709 | #Check if there are Arguments defined, and set the variable accordingly
710 | if $pBuddy -c "Print :${1}:${currentIndex}:Arguments" "$BaselineConfig" > /dev/null 2>&1; then
711 | #This script has arguments defined
712 | currentArguments=$($pBuddy -c "Print :${1}:${currentIndex}:Arguments" "$BaselineConfig")
713 | else
714 | #This script does not have arguments defined
715 | currentArguments=""
716 | fi
717 | #Now we have to do a trick in case there are multiple arguments, some of which are quoted together
718 | #Consider: /path/to/script.sh --font "Times New Roman"
719 | #Used the eval trick outlined here: https://superuser.com/questions/1066455/how-to-split-a-string-with-quotes-like-command-arguments-in-bash
720 | currentArgumentArray=()
721 | if [ -n "$currentArguments" ]; then
722 | eval 'for argument in '$currentArguments'; do currentArgumentArray+=$argument; done'
723 | fi
724 |
725 | #Update the dialog window so that this item shows as "pending"
726 | dialog_command "listitem: title: $currentDisplayName, status: wait"
727 |
728 | #Only set the progress label if we're processing Scripts, not InitialScripts since users won't see those
729 | if [ "$1" = "Scripts" ]; then
730 | set_progressbar_text "$currentDisplayName"
731 | fi
732 |
733 | #Call our script with our desired options. Default options first, so that they can be overriden by "currentArguments"
734 | if [[ $asUser == "true" ]]; then
735 | if [[ -z "$currentUser" ]]; then
736 | # If we don't have a user, we don't want to run this script
737 | log_message "Script set to run asUser but no user logged in: $currentScript"
738 | /usr/bin/false
739 | else
740 | log_message "Running Script: $currentScript as $currentUser with arguments: ${currentArgumentArray[@]}"
741 | /bin/launchctl asuser "$currentUserUID" sudo -u "$currentUser" "$currentScript" ${currentArgumentArray[@]} >> "$ScriptOutputLog" 2>&1
742 | fi
743 | else
744 | log_message "Running Script: $currentScript as root with arguments: ${currentArgumentArray[@]}"
745 | "$currentScript" ${currentArgumentArray[@]} >> "$ScriptOutputLog" 2>&1
746 | fi
747 | scriptExitCode=$?
748 | if [ $scriptExitCode != 0 ]; then
749 | report_message "Failed Item - Script runtime error: $currentScript - Exit Code: $scriptExitCode"
750 | dialog_command "listitem: title: $currentDisplayName, status: fail"
751 | failList+=("$currentDisplayName")
752 | else
753 | report_message "Successful Item - Script: $currentScript"
754 | dialog_command "listitem: title: $currentDisplayName, status: success"
755 | successList+=("$currentDisplayName")
756 | fi
757 | update_tracker $currentDisplayName $scriptExitCode
758 |
759 | #Iterate index for next loop
760 | currentIndex=$((currentIndex+1))
761 |
762 | # This gets set for use with the BailOut feature
763 | previousDisplayName="$currentDisplayName"
764 |
765 | #Stuff in this section only happens if we're processing Scripts and not InitialScripts
766 | if [ "$1" = "Scripts" ]; then
767 | increment_progress_bar
768 | #If we're using jamf, and jamf verbose is configured
769 | if [[ ${currentScriptPath} == "/usr/local/bin/jamf" ]] && $showVerboseJamf; then
770 | # If the PID is still running, kill it
771 | if ps -x "$jamfVerbosePID" > /dev/null 2>&1; then
772 | kill "$jamfVerbosePID"
773 | fi
774 | # Now clear the jamf verbose status text
775 | dialog_command "listitem: title: ${currentDisplayName}, statustext: "
776 | fi
777 | fi
778 | done
779 | }
780 |
781 | function process_pkgs(){
782 | #Set an index internal to this function
783 | currentIndex=0
784 | #Loop through and test if there is a value in the slot of this index for the given array
785 | #If this command fails it means we've reached the end of the array in the config file (or there are none) and we exit our loop
786 | while $pBuddy -c "Print :Packages:${currentIndex}" "$BaselineConfig" > /dev/null 2>&1; do
787 | check_for_bail_out
788 | # Unset variables for next loop
789 | unset currentPKG
790 | unset currentPKGPath
791 | unset expectedTeamID
792 | unset expectedMD5
793 | unset actualMD5
794 | unset expectedSHA256
795 | unset actualSHA256
796 | unset actualTeamID
797 | unset currentArguments
798 | unset currentArgumentArray
799 | unset currentDisplayName
800 | unset pkgBasename
801 | unset downloadResult
802 |
803 | #Get the display name of the label we're installing. We need this to update the dialog list
804 | currentDisplayName=$($pBuddy -c "Print :Packages:${currentIndex}:DisplayName" "$BaselineConfig")
805 | #Set the current package path
806 | currentPKGPath=$($pBuddy -c "Print :Packages:${currentIndex}:PackagePath" "$BaselineConfig")
807 |
808 | ##Here is where we begin checking what kind of PKG was defined, and how to process it
809 | ##The end result of this chunk of code, is that we have a valid path to a PKG on the file system
810 | ##Else we bail and continue looping to install the next item
811 |
812 | #Check if the package path is a web URL
813 | if [[ ${currentPKGPath:0:4} == "http" ]]; then
814 | # The path to the PKG appears to be a URL.
815 | #Get the basename of the .pkg we're downloading
816 | pkgBasename=$(basename "$currentPKGPath")
817 | #Set the "currentPKG" variable, this gets used as the download path as well as processed later
818 | currentPKG="$BaselinePackages"/"$pkgBasename"
819 | #Check for conflict. If there's already a PKG in the directory we're downloading to, delete it
820 | rm_if_exists "$currentPKG"
821 | #Perform the download of the remote pkg
822 | curl -LJs "$currentPKGPath" -o "$currentPKG"
823 | #Capture the output of our curl command
824 | downloadResult=$?
825 | #Verify curl exited with 0
826 | if [ "$downloadResult" != 0 ]; then
827 | report_message "Failed Item - Package download error: $currentPKGPath"
828 | # Iterate the index up one
829 | currentIndex=$((currentIndex+1))
830 | increment_progress_bar
831 | # Report the fail
832 | dialog_command "listitem: title: $currentDisplayName, status: fail"
833 | # Bail this pass through the while loop and continue processing next item
834 | continue
835 | else
836 | debug_message "PKG downloaded successfully: $currentPKGPath"
837 | fi
838 | fi
839 |
840 | # Check if the pkg exists
841 | if [ -e "$currentPKG" ]; then
842 | debug_message "PKG found: $currentPKG"
843 | elif [ -e "$currentPKGPath" ]; then
844 | # The path to the PKG appears to exist on the local file system
845 | currentPKG="$currentPKGPath"
846 | elif [ -e "$BaselinePackages/$currentPKGPath" ]; then
847 | # The path to the PKG appears to exist within Baseline directory
848 | currentPKG="$BaselinePackages/$currentPKGPath"
849 | else
850 | report_message "Failed Item - Package does not exist: $currentPKGPath"
851 | dialog_command "listitem: title: $currentDisplayName, status: fail"
852 | failList+=("$currentDisplayName")
853 | currentIndex=$((currentIndex+1))
854 | increment_progress_bar
855 | update_tracker $currentDisplayName 99
856 | continue
857 | fi
858 |
859 | ##At this point, the pkg exists on the file system, or we've bailed on this loop.
860 |
861 | #Check if there are Arguments defined, and set the variable accordingly
862 | if $pBuddy -c "Print :Packages:${currentIndex}:Arguments" "$BaselineConfig" > /dev/null 2>&1; then
863 | #This pkg has arguments defined
864 | currentArguments=$($pBuddy -c "Print :Packages:${currentIndex}:Arguments" "$BaselineConfig")
865 | else
866 | #This pkg does not have arguments defined
867 | currentArguments=""
868 | fi
869 | #Now we have to do a trick in case there are multiple arguments, some of which are quoted together
870 | #Consider: /path/to/script.sh --font "Times New Roman"
871 | #Used the eval trick outlined here: https://superuser.com/questions/1066455/how-to-split-a-string-with-quotes-like-command-arguments-in-bash
872 | currentArgumentArray=()
873 | eval 'for argument in '$currentArguments'; do currentArgumentArray+=$argument; done'
874 |
875 | if $pBuddy -c "Print :Packages:${currentIndex}:TeamID" "$BaselineConfig" > /dev/null 2>&1; then
876 | #This pkg has TeamID defined
877 | expectedTeamID=$($pBuddy -c "Print :Packages:${currentIndex}:TeamID" "$BaselineConfig")
878 | else
879 | #This pkg does not have TeamID Validation defined
880 | expectedTeamID=""
881 | fi
882 | if $pBuddy -c "Print :Packages:${currentIndex}:SHA256" "$BaselineConfig" > /dev/null 2>&1; then
883 | #This script has SHA256 defined
884 | expectedSHA256=$($pBuddy -c "Print :Packages:${currentIndex}:SHA256" "$BaselineConfig")
885 | else
886 | #This script does not have SHA256 defined
887 | expectedSHA256=""
888 | fi
889 | if $pBuddy -c "Print :Packages:${currentIndex}:MD5" "$BaselineConfig" > /dev/null 2>&1; then
890 | #This script has MD5 defined
891 | expectedMD5=$($pBuddy -c "Print :Packages:${currentIndex}:MD5" "$BaselineConfig")
892 | else
893 | #This script does not have MD5 defined
894 | expectedMD5=""
895 | fi
896 | #Update the dialog window so that this item shows as "pending"
897 | dialog_command "listitem: title: $currentDisplayName, status: wait"
898 | set_progressbar_text "$currentDisplayName"
899 |
900 | ## Package validation happens here
901 | # Check TeamID, if a value has been provided
902 | if [ -n "$expectedTeamID" ]; then
903 | #Get the TeamID for the current PKG
904 | actualTeamID=$(spctl -a -vv -t install "$currentPKG" 2>&1 | awk -F '(' '/origin=/ {print $2 }' | tr -d ')' )
905 | # Check if actual does not match expected
906 | if [ "$expectedTeamID" != "$actualTeamID" ]; then
907 | report_message "Failed Item - Package TeamID error: $currentPKG - Expected - $expectedTeamID Actual - $actualTeamID"
908 | dialog_command "listitem: title: $currentDisplayName, status: fail"
909 | failList+=("$currentDisplayName")
910 | # Iterate the index up one
911 | currentIndex=$((currentIndex+1))
912 | increment_progress_bar
913 | # Report the fail
914 | dialog_command "listitem: title: $currentDisplayName, status: fail"
915 | # Bail this pass through the while loop and continue processing next item
916 | continue
917 | else
918 | log_message "TeamID of PKG validated: $currentPKG $expectedTeamID"
919 | fi
920 | fi
921 |
922 | # Check SHA256, if a value has been provided
923 | if [ -n "$expectedSHA256" ]; then
924 | #Get SHA256 for the current PKG
925 | actualSHA256=$(shasum -a 256 "$currentPKG" | awk '{ print $1 }')
926 | # Check if actual does not match expected
927 | if [ "$expectedSHA256" != "$actualSHA256" ]; then
928 | report_message "Failed Item - Package SHA256 error: $currentPKG - Expected - $expectedSHA256 Actual - $actualSHA256"
929 | dialog_command "listitem: title: $currentDisplayName, status: fail"
930 | failList+=("$currentDisplayName")
931 | # Iterate the index up one
932 | currentIndex=$((currentIndex+1))
933 | increment_progress_bar
934 | # Report the fail
935 | dialog_command "listitem: title: $currentDisplayName, status: fail"
936 | update_tracker $currentDisplayName 99
937 | # Bail this pass through the while loop and continue processing next item
938 | continue
939 | else
940 | log_message "SHA256 of PKG validated: $currentPKG $expectedSHA256"
941 | fi
942 | fi
943 |
944 | # Check MD5, if a value has been provided
945 | if [ -n "$expectedMD5" ]; then
946 | #Get MD5 for the current PKG
947 | actualMD5=$(md5 -q "$currentPKG")
948 | # Check if actual does not match expected
949 | if [ "$expectedMD5" != "$actualMD5" ]; then
950 | report_message "Failed Item - Package MD5 error: $currentPKG - Expected - $expectedMD5 Actual - $actualMD5"
951 | dialog_command "listitem: title: $currentDisplayName, status: fail"
952 | failList+=("$currentDisplayName")
953 | # Iterate the index up one
954 | currentIndex=$((currentIndex+1))
955 | increment_progress_bar
956 | # Report the fail
957 | dialog_command "listitem: title: $currentDisplayName, status: fail"
958 | update_tracker $currentDisplayName 99
959 | # Bail this pass through the while loop and continue processing next item
960 | continue
961 | else
962 | log_message "MD5 of PKG validated: $currentPKG $expectedMD5"
963 | fi
964 | fi
965 |
966 | ## The package installation happens here. We do this in a variable so we can capture the output and report it for debugging
967 | pkgInstallerOutput=$(installer -allowUntrusted -pkg "$currentPKG" -target / ${currentArgumentArray[@]} )
968 | # Capture the installer exit code
969 | pkgExitCode=$?
970 | # Verify the install completed successfully
971 | if [ $pkgExitCode != 0 ]; then
972 | report_message "Failed Item - Package installation error: $currentPKG - Exit Code: $pkgExitCode"
973 | dialog_command "listitem: title: $currentDisplayName, status: fail"
974 | failList+=("$currentDisplayName")
975 | else
976 | report_message "Successful Item - Package: $currentPKG"
977 | dialog_command "listitem: title: $currentDisplayName, status: success"
978 | successList+=("$currentDisplayName")
979 | fi
980 | update_tracker $currentDisplayName $pkgExitCode
981 | debug_message "Output of the install package command: $pkgInstallerOutput"
982 | # Iterate to the next index item, and continue our loop
983 | currentIndex=$((currentIndex+1))
984 | increment_progress_bar
985 | # This gets set for use with the BailOut feature
986 | previousDisplayName="$currentDisplayName"
987 | check_for_bail_out
988 | done
989 | }
990 |
991 | function copy_icons_dir(){
992 | if [ -d "${BaselineIcons}" ]; then
993 | cp -r "${BaselineIcons}/"* "${BaselineTempIconsDir}/"
994 | chmod -R 655 "${BaselineTempIconsDir}"
995 | fi
996 | }
997 |
998 | function build_dialog_json_file(){
999 | # Initiate Json file
1000 | /bin/echo "{\"listitem\" : [" >> $dialogJsonFile
1001 | # For each item in our list, add the Json line
1002 | for jsonItem in $dialogListJson; do
1003 | /bin/echo "$jsonItem" >> $dialogJsonFile
1004 | done
1005 | # This trick removes the final character from the file, to ensure a valid Json
1006 | cat "$dialogJsonFile" | sed '$ s/.$//' > "${BaselineTempDir}/tempJson1"
1007 | mv "${BaselineTempDir}/tempJson1" "$dialogJsonFile"
1008 | # Finish Json file
1009 | /bin/echo "]}" >> "$dialogJsonFile"
1010 |
1011 | # Set global read permissions for Json file
1012 | chmod 644 "$dialogJsonFile"
1013 |
1014 | }
1015 |
1016 | function build_dialog_list_options(){
1017 | # This function populates an array with all of the items Baseline iterates through
1018 | for i in $dialogList; do
1019 | dialogListItems+=(--listitem $i)
1020 | done
1021 | }
1022 |
1023 | function check_exit_condition(){
1024 | exitConditionPath=""
1025 | # If `ExitCondition` key is passed in the configuration profile, then set a variable
1026 | if $pBuddy -c "Print :ExitCondition" "$BaselineConfig" > /dev/null 2>&1; then
1027 | exitConditionPath=$($pBuddy -c "Print :ExitCondition" "$BaselineConfig" | sed 's/"//g' )
1028 | fi
1029 | # If our variable is set, and if the file exists, cleanup and exit quietly
1030 | if [ -n "$exitConditionPath" ] && [ -e "$exitConditionPath" ]; then
1031 | cleanup_and_exit 0 "Exit Condition exists. Exiting: "$exitConditionPath""
1032 | fi
1033 |
1034 | }
1035 |
1036 | function check_bail_out_configuration(){
1037 | # If BailOutFile key has a value, set it for the filepath we'll check
1038 | if $pBuddy -c "Print :BailOutFile" "$BaselineConfig" > /dev/null 2>&1; then
1039 | bailOutFilePath=$($pBuddy -c "Print :BailOutFile" "$BaselineConfig")
1040 | else
1041 | bailOutFilePath=""
1042 | fi
1043 | }
1044 |
1045 | function check_for_bail_out(){
1046 | # If our BailOutFilePath has a value
1047 | if [ ! -z $bailOutFilePath ]; then
1048 | # Check if the file exists
1049 | if [ -f "$bailOutFilePath" ]; then
1050 | # Add the previousDisplayName to our failure list
1051 | failList+=("$previousDisplayName")
1052 | # Delete the bail out file
1053 | rm_if_exists "$bailOutFilePath"
1054 | #Close our running dialog window
1055 | dialog_command "quit:"
1056 | # Do the Failure window
1057 | present_failure_window
1058 | # Exit with code 99
1059 | cleanup_and_restart 99 "Bail out file identified: $bailOutFilePath"
1060 | fi
1061 | fi
1062 | }
1063 |
1064 | function check_restart_option(){
1065 | restartSetting=$($pBuddy -c "Print :Restart" "$BaselineConfig" 2> /dev/null )
1066 | logOutSetting=$($pBuddy -c "Print :LogOut" "$BaselineConfig" 2> /dev/null )
1067 |
1068 |
1069 | if [ -z $logOutSetting ]; then
1070 | log_message "No LogOut key in configuration file"
1071 | forceLogOut="unset"
1072 | elif [[ "$logOutSetting" == "true" ]]; then
1073 | log_message "LogOut set to true from configuration file"
1074 | forceLogOut=true
1075 | elif [[ "$logOutSetting" == "false" ]]; then
1076 | log_message "LogOut set to false from configuration file"
1077 | forceLogOut="false"
1078 | else
1079 | log_message "Invalid value for LogOut key. Setting to default. Invalid Key Value: $logOutSetting"
1080 | forceLogOut="unset"
1081 | fi
1082 |
1083 | if [ -z $restartSetting ]; then
1084 | log_message "No Restart key in configuration file"
1085 | forceRestart="unset"
1086 | elif [[ "$restartSetting" == "false" ]]; then
1087 | log_message "Force Restart set to false from configuration file."
1088 | forceRestart="false"
1089 | elif [[ "$restartSetting" == "true" ]]; then
1090 | log_message "Force Restart set to true from configuration file."
1091 | forceRestart="true"
1092 | else
1093 | log_message "Force Restart setting invalid. Setting default. Invalid Key Value: $restartSetting"
1094 | forceRestart="unset"
1095 | fi
1096 |
1097 | debug_message "Checking exit action variables"
1098 |
1099 | if [[ "$forceRestart" == "unset" ]] && [[ "$forceLogOut" == "unset" ]]; then
1100 | log_message "No Restart or LogOut key in configuration file. Setting default behavior to Restart"
1101 | forceRestart=true
1102 | forceLogOut=false
1103 | elif [[ "$forceRestart" == "true" ]]; then
1104 | log_message "Restart key set to true. Device will be restarted."
1105 | forceRestart=true
1106 | forceLogOut=false
1107 | elif [[ "$forceLogOut" == "true" ]]; then
1108 | log_message "LogOut key set to true. User will be logged out."
1109 | forceRestart=false
1110 | forceLogOut=true
1111 | elif [[ "$forceRestart" == "false" ]]; then
1112 | log_message "Restart key set to false. LogOut key not set to true. No restart and No LogOut will occur."
1113 | forceRestart=false
1114 | forceLogOut=false
1115 | elif [[ "$forceRestart" == "unset" ]] ; then
1116 | log_message "Restart key not in configuration file. LogOut key set to false. Device will be restarted."
1117 | forceRestart=true
1118 | forceLogOut=false
1119 | else
1120 | log_message "Unknown combination of LogOut and Restart values. Open an issue on GitHub and provide logs."
1121 | forceRestart=false
1122 | forceLogOut=false
1123 | fi
1124 |
1125 | }
1126 |
1127 | function check_progress_options(){
1128 | # Set variable for whether or not we'll display a progress bar. Defaults to 'false'
1129 | showProgressBarSetting=$($pBuddy -c "Print :ProgressBar" "$BaselineConfig" 2> /dev/null )
1130 |
1131 | if [[ $showProgressBarSetting == "true" ]]; then
1132 | showProgressBar="true"
1133 | else
1134 | showProgressBar="false"
1135 | fi
1136 |
1137 | # Set variable for whether or not we'll display a progress bar label. Defaults to 'false'
1138 | showProgressBarDisplayNameSetting=$($pBuddy -c "Print :ProgressBarDisplayNames" "$BaselineConfig" 2> /dev/null )
1139 |
1140 | if [[ $showProgressBarDisplayNameSetting == "true" ]]; then
1141 | progressBarDisplayNames="true"
1142 | else
1143 | progressBarDisplayNames="false"
1144 | fi
1145 | }
1146 |
1147 | function increment_progress_bar(){
1148 | # If we're not displaying the progress bar, skip
1149 | if [ "$showProgressBar" != "true" ]; then
1150 | return
1151 | fi
1152 |
1153 | # Increment progress bar
1154 | progressBarValue=$((progressBarValue+1))
1155 | # Do the math to determine total progress bar size for real increment
1156 | progressBarPercentage=$((progressBarValue*100/progressBarTotal))
1157 |
1158 | dialog_command "progress: $progressBarPercentage"
1159 | }
1160 |
1161 | function set_progressbar_text(){
1162 | # If we're not displaying the progress bar, skip
1163 | if [ "$progressBarDisplayNames" != "true" ]; then
1164 | return
1165 | fi
1166 |
1167 | dialog_command "progresstext: $1"
1168 | }
1169 |
1170 | function present_failure_window(){
1171 | #There was at least one failed item. Build fail list
1172 | failListItems=()
1173 | for i in ${failList[@]}; do
1174 | failListItems+=(--listitem $i)
1175 | done
1176 | #Create our Failure Dialog Window. We use a "while" loop and a nested if/then in order to bail if there's a configuration file problem.
1177 | #Set a timer for our attempts
1178 | dialogAttemptCount=1
1179 | #Set our exit variable for the while loop
1180 | dialogCompletionWindow="incomplete"
1181 | #Set our exit condition for the while loop
1182 | while [ $dialogCompletionWindow = "incomplete" ]; do
1183 | #If we haven't tried 10 times yet, then try to call Dialog
1184 | if [ "$dialogAttemptCount" -le 10 ]; then
1185 | #If dialog exits 0, then exit our loop
1186 | ${finalFailureCommand[@]} ${failListItems[@]}
1187 | dialogExitCode=$?
1188 | if [ $dialogExitCode = 0 ] || [ $dialogExitCode = 4 ] || [ $dialogExitCode = 10 ]; then
1189 | dialogCompletionWindow="complete"
1190 | fi
1191 | #Increment our dialog attempt count
1192 | sleep 1
1193 | dialogAttemptCount=$(( dialogAttemptCount +1 ))
1194 | else
1195 | #If we got here, dialog tried 10 times and never opened properly. Exit with a message to the log file.
1196 | cleanup_and_exit 1 "**WARNING** SwiftDialog failed to launch after 10 attempts. This likely indicates an issue with the options in the configuration file. Check your file paths."
1197 | fi
1198 | done
1199 | }
1200 |
1201 | function present_success_window(){
1202 | #Create our Success Dialog Window. We use a "while" loop and a nested if/then in order to bail if there's a configuration file problem.
1203 | #Set a timer for our attempts
1204 | dialogAttemptCount=1
1205 | #Set our exit variable for the while loop
1206 | dialogCompletionWindow="incomplete"
1207 | #Set our exit condition for the while loop
1208 | while [ $dialogCompletionWindow = "incomplete" ]; do
1209 | #If we haven't tried 10 times yet, then try to call Dialog
1210 | if [ "$dialogAttemptCount" -le 10 ]; then
1211 | #If dialog exits 0, then exit our loop
1212 | ${finalSuccessCommand[@]}
1213 | dialogExitCode=$?
1214 | if [ $dialogExitCode = 0 ] || [ $dialogExitCode = 4 ] || [ $dialogExitCode = 10 ]; then
1215 | dialogCompletionWindow="complete"
1216 | fi
1217 | #Increment our dialog attempt count
1218 | sleep 1
1219 | dialogAttemptCount=$(( dialogAttemptCount +1 ))
1220 | else
1221 | #If we got here, dialog tried 10 times and never opened properly. Exit with a message to the log file.
1222 | cleanup_and_exit 1 "**WARNING** SwiftDialog failed to launch after 10 attempts. This likely indicates an issue with the options in the configuration file. Check your file paths."
1223 | fi
1224 | done
1225 | }
1226 |
1227 | function initiate_tracker_file(){
1228 | if "$useTracker"; then
1229 | # Set tracker file path
1230 | trackerFilePath=/var/log/Baseline-$(basename "$BaselineConfig")-tracker.plist
1231 |
1232 | # If tracker file already exists, verify its a valid plist
1233 | if [ -e "$trackerFilePath" ]; then
1234 | if ! $pBuddy -c Print "$trackerFilePath" > /dev/null 2>&1; then
1235 | report_message "Invalid tracker file. Cannot not use tracker feature."
1236 | useTracker=false
1237 | else
1238 | report_message "Tracker file will be used: $trackerFilePath"
1239 | fi
1240 | # Else, create the file. If we can't create it, turn off useTracker
1241 | else
1242 | if ! $pBuddy -c "Add :TrackerCreationDate integer $(date +%s)" "$trackerFilePath" > /dev/null 2>&1; then
1243 | report_message "Cannot write to tracker file. Cannot not use tracker feature."
1244 | useTracker=false
1245 | else
1246 | $pBuddy -c "Add :TrackerCreationDateReadable string $(date)" "$trackerFilePath"
1247 | report_message "Tracker file created: $trackerFilePath"
1248 | fi
1249 | fi
1250 | fi
1251 |
1252 | }
1253 |
1254 | function update_tracker(){
1255 | # If we're using a tracker
1256 | if $useTracker; then
1257 | currentTrackerName=$(echo "${1}" | tr -d '[:space:]' | sed "s:\'::g" | sed "s:\"::g")
1258 | # If we can't Add the value, it must already exist so we instead set the value.
1259 | if ! $pBuddy -c "Add :$currentTrackerName:LastExitCode integer $2" "$trackerFilePath" > /dev/null 2>&1; then
1260 | $pBuddy -c "Set :$currentTrackerName:LastExitCode $2" "$trackerFilePath"
1261 | fi
1262 | if ! $pBuddy -c "Add :$currentTrackerName:LastRun integer $(date +%s)" "$trackerFilePath" > /dev/null 2>&1; then
1263 | $pBuddy -c "Set :$currentTrackerName:LastRun $(date +%s)" "$trackerFilePath"
1264 | fi
1265 | # If the item was successful, update the tracker to reflect that
1266 | if [[ $2 = 0 ]]; then
1267 | if ! $pBuddy -c "Add :$currentTrackerName:LastSuccessfulCompletion integer $(date +%s)" "$trackerFilePath" > /dev/null 2>&1; then
1268 | $pBuddy -c "Set :$currentTrackerName:LastSuccessfulCompletion $(date +%s)" "$trackerFilePath"
1269 | fi
1270 | if ! $pBuddy -c "Add :$currentTrackerName:LastSuccessfulCompletionReadable string $(date)" "$trackerFilePath" > /dev/null 2>&1; then
1271 | $pBuddy -c "Set :$currentTrackerName:LastSuccessfulCompletionReadable $(date)" "$trackerFilePath"
1272 | fi
1273 | else
1274 | # Update the tracker to show 0/never if the item has never completed successfully
1275 | if ! $pBuddy -c "Print :$currentTrackerName:LastSuccessfulCompletion" "$trackerFilePath" > /dev/null 2>&1; then
1276 | $pBuddy -c "Add :$currentTrackerName:LastSuccessfulCompletion integer 0" "$trackerFilePath"
1277 | fi
1278 | if ! $pBuddy -c "Print :$currentTrackerName:LastSuccessfulCompletionReadable" "$trackerFilePath" > /dev/null 2>&1; then
1279 | $pBuddy -c "Add :$currentTrackerName:LastSuccessfulCompletionReadable string never" "$trackerFilePath"
1280 | fi
1281 | fi
1282 |
1283 | fi
1284 | }
1285 |
1286 | function check_silent_option(){
1287 | # If silentMode hasn't been set yet, check if its in our configuration file.
1288 | if [ -z "$silentModeEnabled" ]; then
1289 | # This will exit empty if the key does not exist.
1290 | silentModeEnabled=$($pBuddy -c "Print :SilentMode" "$BaselineConfig" 2> /dev/null )
1291 | fi
1292 |
1293 | if [ -z $silentModeEnabled ]; then
1294 | log_message "No SilentMode key in configuration file"
1295 | silentModeEnabled="false"
1296 | elif [[ "$silentModeEnabled" == "true" ]]; then
1297 | log_message "SilentMode set to true"
1298 | silentModeEnabled=true
1299 | elif [[ "$silentModeEnabled" == "false" ]]; then
1300 | log_message "SilentMode set to false from configuration file"
1301 | silentModeEnabled="false"
1302 | else
1303 | log_message "Invalid value for SilentMode key. Setting to default. Invalid Key Value: $silentModeEnabled"
1304 | silentModeEnabled="false"
1305 | fi
1306 |
1307 | configure_silent_mode
1308 | }
1309 |
1310 | function configure_silent_mode(){
1311 | # If silentMode is enabled, rewrite all functions which use SwiftDialog to `true`
1312 | # This effectively takes SwiftDialog entirely out of use.
1313 | if $silentModeEnabled; then
1314 |
1315 | dialogPath=true
1316 | dialogAppPath="/System/Applications"
1317 |
1318 | function dialog_command(){
1319 | true
1320 | }
1321 |
1322 | function install_dialog(){
1323 | true
1324 | }
1325 |
1326 | function wait_for_user(){
1327 | true
1328 | }
1329 |
1330 | function build_dialog_json_file(){
1331 | true
1332 | }
1333 |
1334 | function build_dialog_list_options(){
1335 | true
1336 | }
1337 |
1338 | function increment_progress_bar(){
1339 | true
1340 | }
1341 |
1342 | function set_progressbar_text(){
1343 | true
1344 | }
1345 |
1346 | function present_failure_window(){
1347 | true
1348 | }
1349 |
1350 | function present_success_window(){
1351 | true
1352 | }
1353 |
1354 | fi
1355 | }
1356 |
1357 | function jamf_verbose_dialog(){
1358 | # This function reads the jamf log output and updates the associated item in swiftDialog with verbose details like Installomator uses
1359 | # $1 is the DisplayName of the item we want to update
1360 | # Tail the Jamf log file
1361 | tail -Fn0 "$jamfLogFile" | while read line ; do
1362 | # Check for "Executing" status
1363 | if echo "$line" | grep -qi "executing"; then
1364 | dialog_command "listitem: title: ${1}, statustext: Executing..., progress: 25"
1365 | fi
1366 |
1367 | # Check for "Verifying" status
1368 | if echo "$line" | grep -qi "verifying"; then
1369 | dialog_command "listitem: title: ${1}, statustext: Verifying..., progress: 50"
1370 | fi
1371 |
1372 | # Check for "Installing" status
1373 | if echo "$line" | grep -qi "installing"; then
1374 | dialog_command "listitem: title: ${1}, statustext: Installing..., progress: 75"
1375 | fi
1376 |
1377 | # Check for "Successfully installed" status
1378 | if echo "$line" | grep -qi "successfully installed"; then
1379 | dialog_command "listitem: title: ${1}, statustext: Installation Finished"
1380 | break # Exit after successful installation
1381 | fi
1382 |
1383 | # Check for "Failed" status
1384 | if echo "$line" | grep -qi "failed"; then
1385 | dialog_command "listitem: title: ${1}, statustext: Installation Failed"
1386 | break # Exit on failure
1387 | fi
1388 | done
1389 | sleep 1
1390 |
1391 | }
1392 |
1393 | function check_jamf_verbose_option(){
1394 | # Set variable for whether or not we'll use Jamf Verbosle Options. Defaults to 'false'
1395 | showVerboseJamfSetting=$($pBuddy -c "Print :JamfVerbose" "$BaselineConfig" 2> /dev/null )
1396 |
1397 | if [[ $showVerboseJamfSetting == "true" ]]; then
1398 | showVerboseJamf="true"
1399 | else
1400 | showVerboseJamf="false"
1401 | fi
1402 |
1403 | }
1404 |
1405 | function process_wait_for_items(){
1406 | # See if any `WaitFor` items are in our config, if not end this function
1407 | if ! $pBuddy -c "Print :WaitFor:0" "$BaselineConfig" > /dev/null 2>&1; then
1408 | debug_message "No WaitFor items found"
1409 | return 0
1410 | else
1411 | debug_message "WaitFor values found. Initiating WaitFor"
1412 | fi
1413 |
1414 | # Initiate empty arrays
1415 | waitForPaths=()
1416 | waitForDisplayNames=()
1417 |
1418 | # This is our index as we build our arrays
1419 | waitCount=0
1420 |
1421 | # Put all Paths/DisplayNames into our arrays
1422 | while "$pBuddy" -c "Print WaitFor:${waitCount}:Path" "$BaselineConfig" > /dev/null 2>&1; do
1423 | waitForPaths+=$("$pBuddy" -c "Print WaitFor:${waitCount}:Path" "$BaselineConfig")
1424 | waitForDisplayNames+=$("$pBuddy" -c "Print WaitFor:${waitCount}:DisplayName" "$BaselineConfig")
1425 | waitCount=$(( waitCount + 1 ))
1426 | done
1427 |
1428 | # Put all of our WaitFor items into spinny wait mode
1429 | #for waitForDisplayName in "${waitForDisplayNames[@]}"; do
1430 | # dialog_command "listitem: title: $waitForDisplayName, status: wait"
1431 | #done
1432 |
1433 | # While we still have paths we're waiting for
1434 | while [ -n "$waitForPaths" ] ; do
1435 | # Check for each path in our Paths array, and see if it exists yet
1436 | for waitPath in "${waitForPaths[@]}"; do
1437 | # If our item exists
1438 | if [ -e "$waitPath" ]; then
1439 | debug_message "$waitPath exists"
1440 | # Find what index in our array the current item belongs to
1441 | indexItemToRemove="${waitForPaths[(Ie)${waitPath}]}"
1442 | # As long as that index is not zero
1443 | if [ "$indexItemToRemove" != 0 ]; then
1444 | # Remove the Path and the DisplayName from the list of items we're waiting to complete
1445 | debug_message "Removing from waitForPaths: $waitForPaths[${indexItemToRemove}] index: ${indexItemToRemove}"
1446 | debug_message "Removing from waitForDisplayNames: $waitForDisplayNames[${indexItemToRemove}] index: ${indexItemToRemove}"
1447 | # Mark the item as complete
1448 | dialog_command "listitem: title: $waitForDisplayNames[$indexItemToRemove], status: success"
1449 | report_message "Successful Item - WaitFor: $waitForDisplayNames[$indexItemToRemove]"
1450 | successList+=("$waitForDisplayNames[$indexItemToRemove]")
1451 | increment_progress_bar
1452 | sleep 1
1453 | waitForPaths[${indexItemToRemove}]=()
1454 | waitForDisplayNames[${indexItemToRemove}]=()
1455 | fi
1456 | fi
1457 | done
1458 | # Sleep 2 seconds between checking for all items
1459 | sleep 2
1460 | done
1461 |
1462 |
1463 | }
1464 |
1465 | ########################################################################################################
1466 | ########################################################################################################
1467 | ##
1468 | ## SCRIPT STARTS HERE
1469 | ##
1470 | ########################################################################################################
1471 | ########################################################################################################
1472 |
1473 | debug_message "Starting script actions"
1474 |
1475 | #Verify we're running as root
1476 | check_root
1477 |
1478 | #Check if exit condition has been defined
1479 | check_exit_condition
1480 |
1481 | #No falling asleep on the job, bud
1482 | no_sleeping
1483 |
1484 | #Set trap so that things always exit cleanly
1485 | trap cleanup_and_exit 1 2 3 6
1486 |
1487 | #Check if directories for Packages and Scripts exist already.
1488 | #This is useful for testing, or if running the script directly (not the pkg)
1489 | make_directory "$BaselineScripts"
1490 | make_directory "$BaselinePackages"
1491 | make_directory "$BaselineIcons"
1492 |
1493 | #Initiate Logging
1494 | initiate_logging
1495 |
1496 | #Setup report
1497 | initiate_report
1498 |
1499 | #################################
1500 | # Process Script Arguments #
1501 | #################################
1502 |
1503 | configFromArgument=false
1504 | useTracker=false
1505 |
1506 | if [ -z $dryRun ]; then
1507 | dryRun=false
1508 | fi
1509 |
1510 | while [ ! -z "$1" ]; do
1511 | case $1 in;
1512 | "/")
1513 | log_message "Shifting arguments for Jamf"
1514 | shift 2
1515 | ;;
1516 | -c|--config|--configuration)
1517 | shift
1518 | if [ -e "$1" ] && $pBuddy -c "Print" "${1}" > /dev/null 2>&1; then
1519 | log_message "Using configuration profile from argument: $1"
1520 | BaselineConfig="$1"
1521 | function verify_configuration_file(){
1522 | true
1523 | }
1524 | configFromArgument=true
1525 | elif [ ! -e "$1" ]; then
1526 | cleanup_and_exit 80 "ERROR: Configuration not found: $1"
1527 | else
1528 | cleanup_and_exit 81 "ERROR: Invalid configuration file: $1"
1529 | fi
1530 | ;;
1531 | -s|--silent|--silent-mode)
1532 | log_message "Setting Silent Mode from Command-Line"
1533 | silentModeEnabled=true
1534 | ;;
1535 | -t|--tracker)
1536 | useTracker=true
1537 | ;;
1538 | *)
1539 | cleanup_and_exit 82 "Unknown argument: $1"
1540 | ;;
1541 | esac
1542 | shift
1543 | done
1544 |
1545 | ############################################################
1546 | # De-Configure Functions and Variables for Silent Mode #
1547 | ############################################################
1548 |
1549 |
1550 |
1551 | #############################################
1552 | # Verify a Configuration File is in Place #
1553 | #############################################
1554 | verify_configuration_file
1555 | check_silent_option
1556 | initiate_tracker_file
1557 |
1558 | #############################################
1559 | # Configure Default Installomator Options #
1560 | #############################################
1561 |
1562 | # Set variable for whether or not we'll use Baseline default Installomator options
1563 | ignoreInstallomatorOptionsSetting=$($pBuddy -c "Print :IgnoreDefaultInstallomatorOptions" "$BaselineConfig" 2> /dev/null )
1564 |
1565 | if [[ $ignoreInstallomatorOptionsSetting == "true" ]]; then
1566 | defaultInstallomatorOptions=()
1567 | else
1568 | defaultInstallomatorOptions=(
1569 | BLOCKING_PROCESS_ACTION=kill
1570 | NOTIFY=silent
1571 | )
1572 | fi
1573 |
1574 | if [ "$dryRun" = true ]; then
1575 | defaultInstallomatorOptions+="DEBUG=2"
1576 | fi
1577 |
1578 | ###########################
1579 | # Install Installomator #
1580 | ###########################
1581 | #If Installomator is going to be used, install it now
1582 | if $pBuddy -c "Print :Installomator:0" "$BaselineConfig" > /dev/null 2>&1; then
1583 | install_installomator
1584 | fi
1585 |
1586 | #########################
1587 | # Install SwiftDialog #
1588 | #########################
1589 | install_dialog
1590 | #If swiftDialog still isn't installed, exit with an error
1591 | if [ ! -e "$dialogAppPath" ]; then
1592 | cleanup_and_exit 1 "ERROR: SwiftDialog failed to install after numerous attempts. Exiting."
1593 | fi
1594 |
1595 | #############################################
1596 | # Wait until a user is verified logged in #
1597 | #############################################
1598 | wait_for_user
1599 |
1600 | # Get the currently logged in user home folder and UID
1601 | currentUser=$( echo "show State:/Users/ConsoleUser" | scutil | awk '/Name :/ { print $3 }' )
1602 | currentUserUID=$(/usr/bin/id -u "$currentUser")
1603 | userHomeFolder=$(dscl . -read /users/${currentUser} NFSHomeDirectory | cut -d " " -f 2)
1604 |
1605 | #############
1606 | # Arrays #
1607 | #############
1608 |
1609 | # Initiate arrays
1610 | dialogList=()
1611 | dialogListItems=()
1612 | dialogListJson=()
1613 | failList=()
1614 | successList=()
1615 |
1616 | installomatorLabels=()
1617 | installomatorOptions=()
1618 |
1619 | scriptsToProcess=()
1620 | scriptArguments=()
1621 |
1622 | pkgsToInstall=()
1623 | pkgValidations=()
1624 |
1625 | ######################
1626 | # Integers and Bools #
1627 | ######################
1628 |
1629 | # Initiate integers
1630 | progressBarValue=0
1631 | progressBarTotal=0
1632 |
1633 | # Initiate bools
1634 | showProgressBar="false"
1635 | progressBarDisplayNames="false"
1636 |
1637 | ##############################
1638 | # Process Initial Scripts #
1639 | ##############################
1640 |
1641 | # Check Bail Out configuration
1642 | check_bail_out_configuration
1643 |
1644 | process_scripts InitialScripts
1645 |
1646 | #Check if a custom plist was delivered/altered during InitialScripts
1647 | check_for_custom_plist
1648 |
1649 | #We check for Installomator again, to support custom plist swapping
1650 | if $pBuddy -c "Print :Installomator:0" "$BaselineConfig" > /dev/null 2>&1; then
1651 | install_installomator
1652 | fi
1653 |
1654 | #######################
1655 | # Customizations #
1656 | #######################
1657 | # Check if we are going to restart. This has to be here, because the Dialog customizations depend on it
1658 | check_restart_option
1659 |
1660 | # Check if we should display a progress bar under the UI
1661 | check_progress_options
1662 |
1663 | # Check if there is an Icons directory, and if so make a temporary copy of it
1664 | copy_icons_dir
1665 |
1666 | #####################################
1667 | # Initiate Dialog Option Arays #
1668 | #####################################
1669 |
1670 | finalListCommand=()
1671 | finalSuccessCommand=()
1672 | finalFailureCommand=()
1673 |
1674 | finalListCommand+="$dialogPath"
1675 | finalSuccessCommand+="$dialogPath"
1676 | finalFailureCommand+="$dialogPath"
1677 |
1678 |
1679 | ######################################
1680 | # Configure List Customizations #
1681 | ######################################
1682 |
1683 | # Configure Installomator SwiftDialog Integration
1684 | useInstallomatorSwiftDialogIntegration=$($pBuddy -c "Print :InstallomatorSwiftDialogIntegration" "$BaselineConfig" 2> /dev/null)
1685 |
1686 | if [[ $useInstallomatorSwiftDialogIntegration == "true" ]]; then
1687 | true
1688 | fi
1689 |
1690 | # Configure Blur Screen options
1691 | blurScreen=$($pBuddy -c "Print :BlurScreen" "$BaselineConfig" 2> /dev/null)
1692 |
1693 | if [[ $blurScreen == "false" ]]; then
1694 | true
1695 | else
1696 | finalListCommand+="--blurscreen"
1697 | finalSuccessCommand+="--blurscreen"
1698 | finalFailureCommand+="--blurscreen"
1699 | fi
1700 |
1701 | # Configure Blur Screen options
1702 | button1enabled=$($pBuddy -c "Print :Button1Enabled" "$BaselineConfig" 2> /dev/null)
1703 |
1704 | if [[ $button1enabled == "true" ]]; then
1705 | true
1706 | else
1707 | finalListCommand+="--button1disabled"
1708 | fi
1709 |
1710 | # Read the Dialog List `Arguments` customizations, if there are any
1711 | if $pBuddy -c "Print DialogListOptions" "$BaselineConfig" > /dev/null 2>&1; then
1712 | dialogListArguments=$($pBuddy -c "Print DialogListOptions" "$BaselineConfig")
1713 | fi
1714 |
1715 | # If we found customizations, read them into our final list command array
1716 | if [ -n "$dialogListArguments" ]; then
1717 | eval 'for customization in '$dialogListArguments'; do finalListCommand+=$customization; done'
1718 | fi
1719 |
1720 | # This function is not with the rest, but it makes the most sense as I add on this feature.
1721 | # I also don't know how to set the variable by passing the name of that variable as an argument to the function.
1722 | # So I'll have to define this function several times.
1723 | # I grow old … I grow old …I shall wear the bottoms of my trousers rolled..
1724 | function configure_dialog_list_arguments(){
1725 | # $1 is the SwiftDialog option to change, $2 is the default value for that option if its not included in the profile
1726 | if (($dialogListArguments[(Ie)$1])); then
1727 | # $1 was included in the customization, so we report it and move along
1728 | log_message "Dialog List Customization Found: $1"
1729 | else
1730 | # $1 wasn't included in the customization options, so we'll set the default value
1731 | finalListCommand+="$1"
1732 | finalListCommand+="$2"
1733 | fi
1734 | }
1735 |
1736 | # Adjust language of our list view window depending on whether or not the device will restart/logout
1737 | defaultListMessage="Feel free to step away, this could take 30 minutes or more."
1738 | if $forceLogOut; then
1739 | # Add a line break and a sentence about logging out
1740 | defaultListMessage+="\n\nYou will be logged out when it's ready for use."
1741 | elif $forceRestart; then
1742 | # Add a line break and a sentence about restarting.
1743 | defaultListMessage+="\n\nYour computer will restart when it's ready for use."
1744 | fi
1745 |
1746 | configure_dialog_list_arguments "--title" "Your computer setup is underway"
1747 | configure_dialog_list_arguments "--message" "$defaultListMessage"
1748 | configure_dialog_list_arguments "--icon" "/System/Library/CoreServices/KeyboardSetupAssistant.app/Contents/Resources/AppIcon.icns"
1749 | configure_dialog_list_arguments "--width" 900
1750 | configure_dialog_list_arguments "--height" 550
1751 | configure_dialog_list_arguments "--quitkey" ']'
1752 |
1753 | if [ "$showProgressBar" = "true" ]; then
1754 | configure_dialog_list_arguments "--progress"
1755 | fi
1756 |
1757 | if [ "$progressBarDisplayNames" = "true" ]; then
1758 | configure_dialog_list_arguments "--progresstext" ' '
1759 | fi
1760 |
1761 | #########################################
1762 | # Configure Success Customizations #
1763 | #########################################
1764 |
1765 | # Read the Dialog Success Arguments customizations, if there are any
1766 | if $pBuddy -c "Print DialogSuccessOptions" "$BaselineConfig" > /dev/null 2>&1; then
1767 | dialogSuccessArguments=$($pBuddy -c "Print DialogSuccessOptions" "$BaselineConfig")
1768 | fi
1769 |
1770 | # If we found customizations, read them into our final list command array
1771 | if [ -n "$dialogSuccessArguments" ]; then
1772 | eval 'for customization in '$dialogSuccessArguments'; do finalSuccessCommand+=$customization; done'
1773 | fi
1774 |
1775 | function configure_dialog_success_arguments(){
1776 | # $1 is the SwiftDialog option to change, $2 is the default value for that option if its not included in the profile
1777 | if (($dialogSuccessArguments[(Ie)$1])); then
1778 | # $1 was included in the customization, so we report it and move along
1779 | log_message "Dialog Success Customization Found: $1"
1780 | else
1781 | # $1 wasn't included in the customization options, so we'll set the default value
1782 | finalSuccessCommand+="$1"
1783 | finalSuccessCommand+="$2"
1784 | fi
1785 | }
1786 |
1787 | configure_dialog_success_arguments "--title" "Your computer setup is complete"
1788 | configure_dialog_success_arguments "--icon" "/System/Library/CoreServices/KeyboardSetupAssistant.app/Contents/Resources/AppIcon.icns"
1789 | configure_dialog_success_arguments "--quitkey" ']'
1790 | # Different values for --message and --button1text if we're forcing log out or restart
1791 | if $forceLogOut; then
1792 | configure_dialog_success_arguments "--message" "You must log out before you can begin using your computer."
1793 | configure_dialog_success_arguments "--button1text" "Log Out Now"
1794 | configure_dialog_success_arguments "--timer" "120"
1795 | fi
1796 |
1797 | if $forceRestart; then
1798 | configure_dialog_success_arguments "--message" "Your device needs to restart before you can begin use."
1799 | configure_dialog_success_arguments "--button1text" "Restart Now"
1800 | configure_dialog_success_arguments "--timer" "120"
1801 | else
1802 | configure_dialog_success_arguments "--message" "Your device is ready for you."
1803 | fi
1804 |
1805 |
1806 | #########################################
1807 | # Configure Failure Customizations #
1808 | #########################################
1809 |
1810 | # Read the Dialog Failure Arguments customizations, if there are any
1811 | if $pBuddy -c "Print DialogFailureOptions" "$BaselineConfig" > /dev/null 2>&1; then
1812 | dialogFailureArguments=$($pBuddy -c "Print DialogFailureOptions" "$BaselineConfig")
1813 | fi
1814 |
1815 | # If we found customizations, read them into our final list command array
1816 | if [ -n "$dialogFailureArguments" ]; then
1817 | eval 'for customization in '$dialogFailureArguments'; do finalFailureCommand+=$customization; done'
1818 | fi
1819 |
1820 | function configure_dialog_failure_arguments(){
1821 | # $1 is the SwiftDialog option to change, $2 is the default value for that option if its not included in the profile
1822 | if (($dialogFailureArguments[(Ie)$1])); then
1823 | # $1 was included in the customization, so we report it and move along
1824 | log_message "Dialog Failure Customization Found: $1"
1825 | else
1826 | # $1 wasn't included in the customization options, so we'll set the default value
1827 | finalFailureCommand+="$1"
1828 | finalFailureCommand+="$2"
1829 | fi
1830 | }
1831 |
1832 | configure_dialog_failure_arguments "--title" "Your computer setup is complete"
1833 | configure_dialog_failure_arguments "--icon" "/System/Library/CoreServices/KeyboardSetupAssistant.app/Contents/Resources/AppIcon.icns"
1834 | configure_dialog_failure_arguments "--message" "Your computer setup is complete, however not everything was installed as expected. Review the list below, and contact IT if you need assistance."
1835 | configure_dialog_failure_arguments "--quitkey" ']'
1836 |
1837 | # Different values for --message and --button1text if we're forcing log out or restart
1838 | if $forceLogOut; then
1839 | configure_dialog_failure_arguments "--button1text" "Log Out Now"
1840 | configure_dialog_failure_arguments "--timer" "120"
1841 | fi
1842 |
1843 | if $forceRestart; then
1844 | configure_dialog_failure_arguments "--button1text" "Restart Now"
1845 | configure_dialog_failure_arguments "--timer" "120"
1846 | fi
1847 |
1848 |
1849 | ###################
1850 | # Build Arrays #
1851 | ###################
1852 | # Build dialogList array by reading our configuration and looping through things
1853 |
1854 | build_dialog_array Installomator
1855 | build_dialog_array Packages
1856 | build_dialog_array Scripts
1857 | build_dialog_array WaitFor
1858 | build_dialog_json_file
1859 | build_dialog_list_options
1860 |
1861 |
1862 | ##################################
1863 | # Draw our dialog list window #
1864 | ##################################
1865 |
1866 | #Create our initial Dialog Window. Do this in an "until" loop, and attempts 10 times before exiting in case it fails to launch for some reason
1867 | dialogAttemptCount=1
1868 | if ! $silentModeEnabled; then
1869 | until pgrep -q -f "$dialogAppPath.* --commandfile $dialogCommandFile --jsonfile $dialogJsonFile"; do
1870 | if [ "$dialogAttemptCount" -le 10 ]; then
1871 | ${finalListCommand[@]} \
1872 | --commandfile "$dialogCommandFile" \
1873 | --jsonfile "$dialogJsonFile" \
1874 | & sleep 1
1875 | dialogAttemptCount=$(( dialogAttemptCount +1 ))
1876 | else
1877 | cleanup_and_exit 1 "**WARNING** SwiftDialog failed to launch after 10 attempts. This likely indicates an issue with the options in the configuration file. Check your file paths."
1878 | fi
1879 | done
1880 | fi
1881 |
1882 | #########################
1883 | # Install the things #
1884 | #########################
1885 |
1886 | # Progress Bar will be pulsing until a value is set
1887 | if [ "$showProgressBar" = "true" ]; then
1888 | dialog_command "progress: 1"
1889 | fi
1890 |
1891 | # Check Bail Out configuration
1892 | check_bail_out_configuration
1893 |
1894 | ## Setup WaitFor loop
1895 | # Check for a custom "WaitForTimeout" value
1896 | waitForTimeoutSetting=$($pBuddy -c "Print :WaitForTimeout" "$BaselineConfig" 2> /dev/null )
1897 | # If the "WaitForTimeout" value is an integer, set our timeout to that. Otherwise, set to default.
1898 | if [[ "${waitForTimeoutSetting}" =~ '^[0-9]+$' ]] ; then
1899 | waitForTimeout="${waitForTimeoutSetting}"
1900 | else
1901 | waitForTimeout="${defaultWaitForTimeout}"
1902 | fi
1903 | # Set the time at which we'll stop waiting for items by getting the date now and adding the seconds for our deadline
1904 | waitForDateNow=$(date +%s)
1905 | waitForDeadline=$(( waitForDateNow + waitForTimeout ))
1906 |
1907 | process_wait_for_items & waitForPID=$!
1908 |
1909 | # Process Items
1910 | process_installomator_labels
1911 |
1912 | process_pkgs
1913 |
1914 | process_scripts Scripts
1915 |
1916 | # Clear any text off the progress bar
1917 | set_progressbar_text " "
1918 |
1919 | # Starting WaitFor mode
1920 | # Initiate empty arrays
1921 | waitForPaths=()
1922 | waitForDisplayNames=()
1923 | failedWaitDisplayNames=()
1924 |
1925 | # This is our index as we build our arrays
1926 | waitCount=0
1927 |
1928 | # Put all Paths/DisplayNames into our arrays
1929 | while "$pBuddy" -c "Print WaitFor:${waitCount}:Path" "$BaselineConfig" > /dev/null 2>&1; do
1930 | waitForPaths+=$("$pBuddy" -c "Print WaitFor:${waitCount}:Path" "$BaselineConfig")
1931 | waitForDisplayNames+=$("$pBuddy" -c "Print WaitFor:${waitCount}:DisplayName" "$BaselineConfig")
1932 | waitCount=$(( waitCount + 1 ))
1933 | done
1934 |
1935 | # If the wait_for_items function is still running OR we haven't reached our deadline, then sleep a bit
1936 | until ! ps -p $waitForPID > /dev/null || [[ $(date +%s) -gt "${waitForDeadline}" ]]; do
1937 | # Wait for the process to finish
1938 | sleep 2
1939 | done
1940 |
1941 | if ps -p $waitForPID > /dev/null; then
1942 | # If we're here, we've reached our deadline and the WaitFor items are still running
1943 | # Kill the process
1944 | kill $waitForPID
1945 | report_message "WaitFor items timed out with items remaining after $waitForTimeout seconds"
1946 | fi
1947 |
1948 | # If we have WaitFor paths
1949 | if [ -n "$waitForPaths" ] ; then
1950 | # Check for each path in our Paths array, and see if it exists yet
1951 | for waitPath in "${waitForPaths[@]}"; do
1952 | # If our item does not exists
1953 | if [ ! -e "$waitPath" ]; then
1954 | # Find what index in our array the current item belongs to
1955 | indexItemOfFail="${waitForPaths[(Ie)${waitPath}]}"
1956 | # As long as that index is not zero
1957 | if [ "$indexItemOfFail" != 0 ]; then
1958 | # Mark the item as failed
1959 | failedWaitDisplayName="${waitForDisplayNames[${indexItemOfFail}]}"
1960 | report_message "Failed Item - WaitFor: $failedWaitDisplayName"
1961 | failList+=("$failedWaitDisplayName")
1962 | dialog_command "listitem: title: $failedWaitDisplayName, status: fail"
1963 | fi
1964 | fi
1965 | done
1966 | fi
1967 |
1968 | if [ -n "$failedWaitDisplayNames" ]; then
1969 | report_message "One or more failed WaitFor items: $failedWaitDisplayNames"
1970 | fi
1971 |
1972 | # Set custom Dialog icon if it exists
1973 | if [ -e "/Library/Application Support/Dialog/Dialog.png" ]; then
1974 | log_message "Custom Dialog icon found. Applying it."
1975 | "/Library/Application Support/Dialog/Dialog.app/Contents/MacOS/Dialog" --seticon "/Library/Application Support/Dialog/Dialog.png"
1976 | fi
1977 |
1978 | if [ "$dryRun" = true ]; then
1979 | sleep 5
1980 | fi
1981 |
1982 | # Check if we are closing the list window before running Final Scripts
1983 | if $pBuddy -c "Print :CloseListBeforeFinalScripts" "$BaselineConfig" > /dev/null 2>&1; then
1984 | closeListBeforeFinalScripts=$($pBuddy -c "Print :CloseListBeforeFinalScripts" "$BaselineConfig")
1985 | else
1986 | closeListBeforeFinalScripts="false"
1987 | fi
1988 |
1989 | log_message "CloseListBeforeFinalScripts: $closeListBeforeFinalScripts"
1990 |
1991 | # If we are closing the List window before running Final Scripts
1992 | if [[ "$closeListBeforeFinalScripts" == "true" ]]; then
1993 | dialog_command "quit:"
1994 | process_scripts FinalScripts
1995 | else
1996 | process_scripts FinalScripts
1997 | dialog_command "quit:"
1998 | fi
1999 |
2000 | #Do final script swiftDialog stuff
2001 | #If the failList is empty, this means success
2002 | if [ -z "$failList" ]; then
2003 | present_success_window
2004 | update_tracker "Baseline" 0
2005 | # We are done!
2006 | cleanup_and_restart 0 "Baseline completed - All items successful."
2007 | else
2008 | present_failure_window
2009 | update_tracker "Baseline" 1
2010 | # We are done!
2011 | cleanup_and_restart 0 "Baseline completed - Some items failed."
2012 | fi
2013 |
--------------------------------------------------------------------------------