├── .gitattributes
├── .gitignore
├── Build Tools
├── Clean Mac Test Boot Source Drive.applescript
├── Create macOS USB Installer Commands.txt
├── Download-macOS-Installer-DMGs.sh
├── Mac Test Boot Disk Image Commands.txt
├── MacLand Script Builder.applescript
└── update-mac-drives.sh
├── LICENSE
├── Other Scripts
├── every_apple_marketing_model_name_with_grouped_serial_config_codes.txt
├── every_apple_serial_config_code_with_marketing_model_name.txt
├── every_intel_mac_marketing_model_name_with_grouped_model_ids_and_serial_config_codes.txt
├── every_mac_marketing_model_name_with_grouped_serial_config_codes.txt
├── generate_csreq_hex_for_tcc_db.jxa
├── get_bluetooth_from_all_mac_specs_pages.sh
├── get_every_apple_serial_config_code_with_marketing_model_name.sh
├── get_marketing_model_name.sh
├── get_power_adapters_from_all_mac_specs_pages.sh
├── get_specs_url_from_serial.sh
├── get_truetone_from_all_mac_specs_pages.sh
├── group_every_intel_mac_marketing_model_name_with_model_ids_and_serial_config_codes.sh
└── quit_apps_by_bundle_id.sh
├── Production Scripts
├── Cleanup After QA Complete
│ └── Source
│ │ └── Cleanup After QA Complete.applescript
├── Free Geek Demo Helper
│ └── Source
│ │ └── Free Geek Demo Helper.applescript
├── Free Geek Login Progress
│ └── Source
│ │ └── Free Geek Login Progress.applescript
├── Free Geek Reset
│ └── Source
│ │ └── Free Geek Reset.applescript
├── Free Geek Setup
│ └── Source
│ │ └── Free Geek Setup.applescript
└── Free Geek Snapshot Helper
│ └── Source
│ └── Free Geek Snapshot Helper.applescript
├── README.md
├── Testing Scripts
├── Audio Test
│ └── Source
│ │ └── Audio Test.applescript
├── CPU Stress Test
│ └── Source
│ │ └── CPU Stress Test.applescript
├── Camera Test
│ └── Source
│ │ └── Camera Test.applescript
├── Free Geek Updater
│ └── Source
│ │ └── Free Geek Updater.applescript
├── GPU Stress Test
│ └── Source
│ │ └── GPU Stress Test.applescript
├── Hard Drive Test
│ └── Source
│ │ └── Extract Info from DriveDx Report.applescript
├── Internet Test
│ └── Source
│ │ └── Internet Test.applescript
├── Keyboard Test
│ └── Source
│ │ ├── build_keyboard_test.sh
│ │ ├── keyboard_test_modifications.css
│ │ └── keyboard_test_modifications.js
├── Mac Scope
│ └── Source
│ │ └── Mac Scope.applescript
├── Microphone Test
│ └── Source
│ │ └── Microphone Test.applescript
├── Screen Test
│ └── Source
│ │ └── Screen Test.applescript
├── Startup Picker
│ └── Source
│ │ └── Startup Picker.applescript
├── Test Boot Setup
│ └── Source
│ │ └── Test Boot Setup.applescript
├── Test CD
│ └── Source
│ │ └── Test CD.applescript
├── Test DVD
│ └── Source
│ │ └── Test DVD.applescript
├── Trackpad Test
│ └── Source
│ │ └── Trackpad Test.applescript
└── Volume Scripts for Breakaway
│ ├── Set Volume for Headphones.applescript
│ └── Set Volume for Speakers.applescript
└── fgMIB Resources
├── Build Tools
├── Text to Speech Phrases.txt
├── build-fg-prepare-os-pkg.sh
└── copy-fgMIB-resources-to-drives.sh
├── Install Packages Script
└── fg-install-packages.sh
├── Prepare OS Package
├── Package Resources
│ ├── fg-error-occurred
│ │ └── fg-error-occurred.sh
│ └── fg-snapshot-reset
│ │ ├── fg-snapshot-preserver.sh
│ │ └── fg-snapshot-reset.sh
└── fg-prepare-os.sh
└── fg-install-os.sh
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.jxa linguist-language=JavaScript
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Ignore from macOS:
2 | .DS_Store
3 |
4 | # Ignore file types in any location:
5 | *.app
6 | *.dmg
7 | *.pkg
8 | *.zip
9 | *.aiff
10 | *.icns
11 | *.png
12 | *.tiff
13 | *.pdf
14 | *.svg
15 | *.pxm
16 | *.iconsproj
17 | *.driveDxLicense
18 | *.preferences
19 |
20 | # Ignore if name starts with in any location:
21 | OLD-*
22 | UNUSED-*
23 |
24 | # Ignore in "Build Tools":
25 | Free\ Geek\ Passwords.plist
26 |
27 | # Ignore in "fgMIB Resources":
28 | extra-bins/
29 | Tools/
30 |
31 | # Ignore in AppleScript Source folders:
32 | DriveDx/
33 | Remote\ Management\ Debugging/
34 | ZIPs\ for\ Auto-Update/
--------------------------------------------------------------------------------
/Build Tools/Clean Mac Test Boot Source Drive.applescript:
--------------------------------------------------------------------------------
1 | --
2 | -- MIT License
3 | --
4 | -- Copyright (c) 2021 Free Geek
5 | --
6 | -- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"),
7 | -- to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
8 | -- and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
9 | --
10 | -- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
11 | --
12 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
13 | -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
14 | -- WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
15 | --
16 |
17 | use AppleScript version "2.7"
18 | use scripting additions
19 |
20 | set bundleIdentifierPrefix to "org.freegeek."
21 |
22 | try
23 | set infoPlistPath to ((POSIX path of (path to me)) & "Contents/Info.plist")
24 | ((infoPlistPath as POSIX file) as alias)
25 |
26 | set AppleScript's text item delimiters to "-"
27 | set correctBundleIdentifier to bundleIdentifierPrefix & ((words of (name of me)) as text)
28 | try
29 | set currentBundleIdentifier to ((do shell script "/usr/libexec/PlistBuddy -c 'Print :CFBundleIdentifier' " & (quoted form of infoPlistPath)) as text)
30 | if (currentBundleIdentifier is not equal to correctBundleIdentifier) then error "INCORRECT Bundle Identifier"
31 | on error
32 | do shell script "plutil -replace CFBundleIdentifier -string " & (quoted form of correctBundleIdentifier) & " " & (quoted form of infoPlistPath)
33 |
34 | try
35 | set currentCopyright to ((do shell script "/usr/libexec/PlistBuddy -c 'Print :NSHumanReadableCopyright' " & (quoted form of infoPlistPath)) as text)
36 | if (currentCopyright does not contain "Twemoji") then error "INCORRECT Copyright"
37 | on error
38 | do shell script "plutil -replace NSHumanReadableCopyright -string " & (quoted form of ("Copyright © " & (year of (current date)) & " Free Geek
39 | Designed and Developed by Pico Mitchell")) & " " & (quoted form of infoPlistPath)
40 | end try
41 |
42 | try
43 | set minSystemVersion to ((do shell script "/usr/libexec/PlistBuddy -c 'Print :LSMinimumSystemVersion' " & (quoted form of infoPlistPath)) as text)
44 | if (minSystemVersion is not equal to "10.13") then error "INCORRECT Minimum System Version"
45 | on error
46 | do shell script "plutil -remove LSMinimumSystemVersionByArchitecture " & (quoted form of infoPlistPath) & "; plutil -replace LSMinimumSystemVersion -string '10.13' " & (quoted form of infoPlistPath)
47 | end try
48 |
49 | try
50 | set prohibitMultipleInstances to ((do shell script "/usr/libexec/PlistBuddy -c 'Print :LSMultipleInstancesProhibited' " & (quoted form of infoPlistPath)) as number)
51 | if (prohibitMultipleInstances is equal to 0) then error "INCORRECT Multiple Instances Prohibited"
52 | on error
53 | do shell script "plutil -replace LSMultipleInstancesProhibited -bool true " & (quoted form of infoPlistPath)
54 | end try
55 |
56 | try
57 | set allowMixedLocalizations to ((do shell script "/usr/libexec/PlistBuddy -c 'Print :CFBundleAllowMixedLocalizations' " & (quoted form of infoPlistPath)) as number)
58 | if (allowMixedLocalizations is equal to 1) then error "INCORRECT Localization"
59 | on error
60 | do shell script "plutil -replace CFBundleAllowMixedLocalizations -bool false " & (quoted form of infoPlistPath) & "; plutil -replace CFBundleDevelopmentRegion -string 'en_US' " & (quoted form of infoPlistPath)
61 | end try
62 |
63 | try
64 | set currentAppleEventsUsageDescription to ((do shell script "/usr/libexec/PlistBuddy -c 'Print :NSAppleEventsUsageDescription' " & (quoted form of infoPlistPath)) as text)
65 | if (currentAppleEventsUsageDescription does not contain (name of me)) then error "INCORRECT AppleEvents Usage Description"
66 | on error
67 | do shell script "plutil -replace NSAppleEventsUsageDescription -string " & (quoted form of ("You MUST click the “OK” button for “" & (name of me) & "” to be able to function.")) & " " & (quoted form of infoPlistPath)
68 | end try
69 |
70 | try
71 | set currentVersion to ((do shell script "/usr/libexec/PlistBuddy -c 'Print :CFBundleShortVersionString' " & (quoted form of infoPlistPath)) as text)
72 | if (currentVersion is equal to "1.0") then error "INCORRECT Version"
73 | on error
74 | set shortCreationDateString to (short date string of (creation date of (info for (path to me))))
75 | set AppleScript's text item delimiters to "/"
76 | set correctVersion to ((text item 3 of shortCreationDateString) & "." & (text item 1 of shortCreationDateString) & "." & (text item 2 of shortCreationDateString))
77 | do shell script "plutil -remove CFBundleVersion " & (quoted form of infoPlistPath) & "; plutil -replace CFBundleShortVersionString -string " & (quoted form of correctVersion) & " " & (quoted form of infoPlistPath)
78 | end try
79 |
80 | -- The "main.scpt" must NOT be writable to prevent the code signature from being invalidated: https://developer.apple.com/library/archive/releasenotes/AppleScript/RN-AppleScript/RN-10_8/RN-10_8.html#//apple_ref/doc/uid/TP40000982-CH108-SW8
81 | do shell script "osascript -e 'delay 0.5' -e 'repeat while (application \"" & (POSIX path of (path to me)) & "\" is running)' -e 'delay 0.5' -e 'end repeat' -e 'try' -e 'do shell script \"chmod a-w \\\"" & ((POSIX path of (path to me)) & "Contents/Resources/Scripts/main.scpt") & "\\\"\"' -e 'do shell script \"codesign -fs \\\"Developer ID Application\\\" --strict \\\"" & (POSIX path of (path to me)) & "\\\"\"' -e 'on error codeSignError' -e 'activate' -e 'display alert \"Code Sign Error\" message codeSignError' -e 'end try' -e 'do shell script \"open -na \\\"" & (POSIX path of (path to me)) & "\\\"\"' > /dev/null 2>&1 &"
82 | quit
83 | delay 10
84 | end try
85 | end try
86 |
87 | -- /Library/Caches/* ??
88 | -- /System/Library/Caches/* ??
89 | -- Delete user caches ??
90 |
91 | set AppleScript's text item delimiters to ""
92 | set tmpPath to ((POSIX path of (((path to temporary items) as text) & "::")) & "fg" & ((words of (name of me)) as text) & "-") -- On Catalina, writing to trailing folder "/TemporaryItems/" often fails with "Operation not permitted" for some reason. Also, prefix all files with "fg" and name of script.
93 |
94 | try -- CLEAN MAC TEST BOOT
95 | (("/Volumes/Mac Test Boot/" as POSIX file) as alias)
96 |
97 | -- Delete a few things from: https://bombich.com/kb/ccc5/some-files-and-folders-are-automatically-excluded-from-backup-task
98 | -- Delete vm and temporary files
99 | -- "com.bombich.ccc" get's created if drive was selected with Carbon Copy Cloner
100 | do shell script ("rm -rf '/Volumes/Mac Test Boot/usr/local/bin/' " & ¬
101 | "'/Volumes/Mac Test Boot/Users/Shared/Build Info/' " & ¬
102 | "'/Volumes/Mac Test Boot/private/var/db/softwareupdate/journal.plist' " & ¬
103 | "'/Volumes/Mac Test Boot/.fseventsd' " & ¬
104 | "'/Volumes/Mac Test Boot/private/var/db/systemstats' " & ¬
105 | "'/Volumes/Mac Test Boot/private/var/db/dyld/dyld_'* " & ¬
106 | "'/Volumes/Mac Test Boot/.VolumeIcon.icns' " & ¬
107 | "'/Volumes/Mac Test Boot/private/var/vm/'* " & ¬
108 | "'/Volumes/Mac Test Boot/private/var/folders/'* " & ¬
109 | "'/Volumes/Mac Test Boot/private/var/tmp/'* " & ¬
110 | "'/Volumes/Mac Test Boot/private/tmp/'* " & ¬
111 | "'/Volumes/Mac Test Boot/Library/Application Support/com.bombich.ccc' " & ¬
112 | "'/Volumes/Mac Test Boot/Users/'*'/Desktop/QA Helper - Computer Specs.txt' " & ¬
113 | "'/Volumes/Mac Test Boot/Users/'*'/Desktop/TESTING' " & ¬
114 | "'/Volumes/Mac Test Boot/Users/'*'/Desktop/REINSTALL' " & ¬
115 | "'/Volumes/Mac Test Boot/Users/'*'/Library/Preferences/ByHost/' " & ¬
116 | "'/Volumes/Mac Test Boot/Users/'*'/Library/Application Support/com.apple.sharedfilelist/' " & ¬
117 | "'/Volumes/Mac Test Boot/Users/'*'/Library/Application Support/App Store/updatejournal.plist' " & ¬
118 | "'/Volumes/Mac Test Boot/Users/'*'/.bash_history' " & ¬
119 | "'/Volumes/Mac Test Boot/Users/'*'/.bash_sessions/' " & ¬
120 | "'/Volumes/Mac Test Boot/Users/'*'/_geeks3d_gputest_log.txt' " & ¬
121 | "'/Volumes/Mac Test Boot/Users/'*'/Library/Safari' " & ¬
122 | "'/Volumes/Mac Test Boot/Users/'*'/Library/Caches/Apple - Safari - Safari Extensions Gallery' " & ¬
123 | "'/Volumes/Mac Test Boot/Users/'*'/Library/Caches/Metadata/Safari' " & ¬
124 | "'/Volumes/Mac Test Boot/Users/'*'/Library/Caches/com.apple.Safari' " & ¬
125 | "'/Volumes/Mac Test Boot/Users/'*'/Library/Caches/com.apple.WebKit.PluginProcess' " & ¬
126 | "'/Volumes/Mac Test Boot/Users/'*'/Library/Cookies/Cookies.binarycookies' " & ¬
127 | "'/Volumes/Mac Test Boot/Users/'*'/Library/Preferences/Apple - Safari - Safari Extensions Gallery' " & ¬
128 | "'/Volumes/Mac Test Boot/Users/'*'/Library/Preferences/com.apple.Safari.LSSharedFileList.plist' " & ¬
129 | "'/Volumes/Mac Test Boot/Users/'*'/Library/Preferences/com.apple.Safari.RSS.plist' " & ¬
130 | "'/Volumes/Mac Test Boot/Users/'*'/Library/Preferences/com.apple.Safari.plist' " & ¬
131 | "'/Volumes/Mac Test Boot/Users/'*'/Library/Preferences/com.apple.Safari.SafeBrowsing.plist' " & ¬
132 | "'/Volumes/Mac Test Boot/Users/'*'/Library/Preferences/com.apple.Safari.SandboxBroker.plist' " & ¬
133 | "'/Volumes/Mac Test Boot/Users/'*'/Library/Preferences/com.apple.SafariBookmarksSyncAgent.plist' " & ¬
134 | "'/Volumes/Mac Test Boot/Users/'*'/Library/Preferences/com.apple.SafariCloudHistoryPushAgent.plist' " & ¬
135 | "'/Volumes/Mac Test Boot/Users/'*'/Library/Preferences/com.apple.WebFoundation.plist' " & ¬
136 | "'/Volumes/Mac Test Boot/Users/'*'/Library/Preferences/com.apple.WebKit.PluginHost.plist' " & ¬
137 | "'/Volumes/Mac Test Boot/Users/'*'/Library/Preferences/com.apple.WebKit.PluginProcess.plist' " & ¬
138 | "'/Volumes/Mac Test Boot/Users/'*'/Library/Preferences/com.apple.SystemProfiler.plist' " & ¬
139 | "'/Volumes/Mac Test Boot/Users/'*'/Library/PubSub/Database' " & ¬
140 | "'/Volumes/Mac Test Boot/Users/'*'/Library/Saved Application State/com.apple.Safari.savedState' " & ¬
141 | "'/Volumes/Mac Test Boot/Users/'*'/Pictures/GPU Stress Test/' " & ¬
142 | "'/Volumes/Mac Test Boot/Users/'*'/Music/iTunes/'")
143 |
144 | tell application id "com.apple.Terminal"
145 | activate
146 | do script "find '/Volumes/Mac Test Boot' -name '.DS_Store' -type f -print -delete"
147 | activate
148 | end tell
149 |
150 | delay 2
151 | end try
152 |
--------------------------------------------------------------------------------
/Build Tools/Create macOS USB Installer Commands.txt:
--------------------------------------------------------------------------------
1 | # THIS IS NOT A SCRIPT.
2 | # THE FOLLOWING COMMANDS ARE INTENDED TO BE COPIED-AND-PASTED INTO TERMINAL WHEN NEEDED.
3 | # BUT, THIS *IS NOT NEEDED ANYMORE* SINCE "update-mac-drives.sh" CAN BE USED INSTEAD.
4 |
5 | # CREATE Sonoma INSTALLER USB (15.2 GB REQUIRED):
6 | sudo '/Volumes/Install macOS Sonoma beta/Install macOS Sonoma beta.app/Contents/Resources/createinstallmedia' --volume /Volumes/InstallSonoma --nointeraction
7 |
8 | # CREATE Ventura INSTALLER USB (14.8 GB REQUIRED):
9 | sudo '/Volumes/Install macOS Ventura/Install macOS Ventura.app/Contents/Resources/createinstallmedia' --volume /Volumes/InstallVentura --nointeraction
10 |
11 | # CREATE Monterey INSTALLER USB (14.7 GB REQUIRED):
12 | sudo '/Volumes/Install macOS Monterey/Install macOS Monterey.app/Contents/Resources/createinstallmedia' --volume /Volumes/InstallMonterey --nointeraction
13 |
14 | # CREATE Big Sur INSTALLER USB (14 GB REQUIRED):
15 | sudo '/Volumes/Install macOS Big Sur/Install macOS Big Sur.app/Contents/Resources/createinstallmedia' --volume /Volumes/InstallBigSur --nointeraction
16 |
17 | # CREATE Catalina INSTALLER USB (9 GB REQUIRED):
18 | sudo '/Volumes/Install macOS Catalina/Install macOS Catalina.app/Contents/Resources/createinstallmedia' --volume /Volumes/InstallCatalina --nointeraction
19 |
20 | # CREATE High Sierra INSTALLER USB (6 GB REQUIRED):
21 | sudo '/Volumes/Install macOS High Sierra/Install macOS High Sierra.app/Contents/Resources/createinstallmedia' --volume /Volumes/InstallHighSierra --nointeraction
22 |
23 |
24 |
25 | # UNUSED INSTALLER COMMANDS
26 |
27 | # CREATE Mojave INSTALLER USB (7 GB REQUIRED):
28 | sudo '/Volumes/Install macOS Mojave/Install macOS Mojave.app/Contents/Resources/createinstallmedia' --volume /Volumes/InstallMojave --nointeraction
29 |
30 | # CREATE Sierra INSTALLER USB (6 GB REQUIRED)
31 | sudo '/Applications/Install macOS Sierra.app/Contents/Resources/createinstallmedia' --applicationpath '/Applications/Install macOS Sierra.app' --volume /Volumes/InstallSierra --nointeraction
32 | # REQUIRED FIX (https://forums.macrumors.com/threads/not-a-valid-volume-mount-point-cant-make-bootable-drive.1935673/page-3?post=28197924#post-28197924):
33 | sudo plutil -replace CFBundleShortVersionString -string '12.6.03' '/Applications/Install macOS Sierra.app/Contents/Info.plist'
34 |
35 | # CREATE El Capitan INSTALLER USB (7 GB REQUIRED):
36 | sudo '/Applications/Install OS X El Capitan.app/Contents/Resources/createinstallmedia' --applicationpath '/Applications/Install OS X El Capitan.app' --volume /Volumes/InstallElCapitan --nointeraction
37 |
38 | # CREATE Yosemite INSTALLER USB (6 GB REQUIRED)
39 | sudo '/Applications/Install OS X Yosemite.app/Contents/Resources/createinstallmedia' --applicationpath '/Applications/Install OS X Yosemite.app' --volume /Volumes/InstallYosemite --nointeraction
40 |
41 |
--------------------------------------------------------------------------------
/Build Tools/Download-macOS-Installer-DMGs.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # shellcheck enable=add-default-case,avoid-nullary-conditions,check-unassigned-uppercase,deprecate-which,quote-safe-variables,require-double-brackets
3 |
4 | #
5 | # MIT License
6 | #
7 | # Copyright (c) 2021 Free Geek
8 | #
9 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"),
10 | # to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
11 | # and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
12 | #
13 | # The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
14 | #
15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
17 | # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
18 | #
19 |
20 | PATH='/usr/bin:/bin:/usr/sbin:/sbin'
21 |
22 | readonly MIST_PATH='/usr/local/bin/mist'
23 |
24 | installer_dmgs_path="${HOME}/Documents/Programming/Free Geek/MacLand Images/macOS Installers"
25 |
26 | declare -a installer_names_to_download=( 'Big Sur' 'Monterey' 'Ventura' 'Sonoma beta' ) # NOT including 'High Sierra' 'Mojave' 'Catalina' anymore since the latest installers are already downloaded and they will never get any new updates.
27 |
28 | for this_installer_name_to_download in "${installer_names_to_download[@]}"; do
29 | catalog_url='https://swscan.apple.com/content/catalogs/others/index-13-12-10.16-10.15-10.14-10.13-10.12-10.11-10.10-10.9-mountainlion-lion-snowleopard-leopard.merged-1.sucatalog'
30 |
31 | mist_list_options=( 'list' 'installer' "${this_installer_name_to_download}" )
32 | if [[ "${this_installer_name_to_download}" == *' beta' ]]; then
33 | catalog_url='https://swscan.apple.com/content/catalogs/others/index-14seed-14-13-12-10.16-10.15-10.14-10.13-10.12-10.11-10.10-10.9-mountainlion-lion-snowleopard-leopard.merged-1.sucatalog.gz'
34 | mist_list_options+=( '-b' )
35 | fi
36 | mist_list_options+=( '-c' "${catalog_url}" '-lqo' 'json' )
37 |
38 | this_installer_info_json="$("${MIST_PATH}" "${mist_list_options[@]}")"
39 |
40 | IFS=$'\n' read -rd '' -a this_installer_info < <(osascript -l 'JavaScript' -e '
41 | function run(argv) {
42 | const latestInstallerDict = JSON.parse(argv[0])[0]
43 | return [latestInstallerDict.name, latestInstallerDict.version, latestInstallerDict.build].join("\n")
44 | }
45 | ' -- "${this_installer_info_json}" 2> /dev/null)
46 | # NOTE: Because of JavaScript behavior, any "undefined" (or "null") values in an array would be turned into empty strings when using "join", making them empty lines.
47 | # And, because of bash behaviors with whitespace IFS treating consecutive whitespace as a single delimiter (explained in https://mywiki.wooledge.org/IFS),
48 | # any empty lines will NOT be included in the bash array being created with this technique to set all lines to an array.
49 | # So, that means if any of these values are not found, the bash array WILL NOT have a count of exactly 3 which we can check to verify all required values were properly loaded.
50 |
51 | if (( ${#this_installer_info[@]} == 3 )); then
52 | this_installer_dmg_name="Install ${this_installer_info[0]} ${this_installer_info[1]}-${this_installer_info[2]}.dmg"
53 |
54 | if [[ -f "${installer_dmgs_path}/${this_installer_dmg_name}" ]]; then
55 | echo "\"${this_installer_dmg_name}\" is up-to-date!"
56 | else
57 | echo "\"${this_installer_dmg_name}\" needs to be downloaded..."
58 | rm -f "${installer_dmgs_path}/Install ${this_installer_info[0]} "*'.dmg' # Delete any outdated installer dmgs.
59 |
60 | mist_download_options=( 'download' 'installer' "${this_installer_info[2]}" 'image' )
61 | if [[ "${this_installer_name_to_download}" == *' beta' ]]; then
62 | mist_download_options+=( '-b' )
63 | fi
64 | mist_download_options+=( '-c' "${catalog_url}" '-o' "${installer_dmgs_path}" )
65 |
66 | sudo "${MIST_PATH}" "${mist_download_options[@]}"
67 | fi
68 | else
69 | echo "\"${this_installer_name_to_download}\" WAS NOT FOUND: $(declare -p this_installer_info | cut -d '=' -f 2-)"
70 | fi
71 | done
72 |
--------------------------------------------------------------------------------
/Build Tools/Mac Test Boot Disk Image Commands.txt:
--------------------------------------------------------------------------------
1 | # THIS IS NOT A SCRIPT.
2 | # THE FOLLOWING COMMANDS ARE INTENDED TO BE COPIED-AND-PASTED INTO TERMINAL WHEN NEEDED.
3 |
4 | # Command to CREATE Mac Test Boot DISK IMAGE from SOURCE drive:
5 |
6 | cd "${HOME}/Documents/Programming/Free Geek/MacLand Images/" && read "?'Mac Test Boot' Device Name (Probably $(diskutil list | awk '/Mac Test Boot/ { print $NF; exit }')): " device_name && echo "$(date '+%Y%m%d')" > '/Volumes/Mac Test Boot/private/var/root/.mtbVersion' && diskutil unmountDisk "${device_name}" || diskutil unmountDisk force "${device_name}"; sudo hdiutil create "FreeGeek-MacTestBoot-HighSierra-$(date +%Y%m%d).dmg" -srcdevice "${device_name}" && sudo asr imagescan --source "FreeGeek-MacTestBoot-HighSierra-$(date +%Y%m%d).dmg"
7 |
8 |
9 | # Command to FORMAT & PARTITION fgMIB + macOS Installers + Mac Test Boot:
10 | # NOTE: For some reason (at least as of macOS 12.6.1 Monterey) each desired size need 0.13G added to it to result in the correct desired size. The sizes (plus 0.13G) being used for each installer are specified in the "Create macOS USB Installer Commands.txt" based on testing to find the minimum required size for each macOS version installer.
11 |
12 | read '?Enter Disk ID to Format and Partition for fgMIB + macOS Installers + Mac Test Boot: disk' disk_id && diskutil partitionDisk "disk${disk_id}" 6 GPT JHFS+ 'fgMIB' 2.13G JHFS+ 'Install macOS Catalina' 9.13G JHFS+ 'Install macOS Big Sur' 14.13G JHFS+ 'Install macOS Monterey' 14.83G JHFS+ 'Install macOS Ventura' 14.93G JHFS+ 'Mac Test Boot' 0B
13 |
14 |
15 |
16 | # NO LONGER USED COMMANDS
17 | # Command to Restore Mac Test Boot
18 |
19 | # VOLUMES ARE SUFFIXED WITH RANDOM NUMBER FOR LATER IDENTIFICATION
20 | # SO MULTIPLE DRIVES CAN GET STARTED AND RUN AT ONCE WITHOUT CONFLICTING
21 | # BUT, THIS *IS NOT NEEDED ANYMORE* SINCE "update-mac-drives.sh" CAN BE USED INSTEAD
22 |
23 | MTB_IMAGE_DATE=20220412; MTB_SOURCE_IMAGE=FreeGeek-MacTestBoot-HighSierra-$MTB_IMAGE_DATE.dmg; RANDOM_SUFFIX=$RANDOM; diskutil rename MTB MTB-$RANDOM_SUFFIX; cd "$HOME/Documents/Programming/Free Geek/MacLand Images/" && echo "RESTORING '$MTB_SOURCE_IMAGE' TO 'MTB-$RANDOM_SUFFIX'" && sudo asr restore --source $MTB_SOURCE_IMAGE --target /Volumes/MTB-$RANDOM_SUFFIX --erase --noprompt
24 |
25 |
26 | # Command to Restore Mac Test Boot with High Sierra, Catalina, and Big Sur Installers
27 | # BUT, THIS *IS NOT NEEDED ANYMORE* SINCE "update-mac-drives.sh" CAN BE USED INSTEAD
28 |
29 | MTB_IMAGE_DATE=20210712; MTB_SOURCE_IMAGE=FreeGeek-MacTestBoot-HighSierra-$MTB_IMAGE_DATE.dmg; RANDOM_SUFFIX=$RANDOM; diskutil rename MTB MTB-$RANDOM_SUFFIX; diskutil rename InstallHighSierra InstallHighSierra-$RANDOM_SUFFIX; diskutil rename InstallCatalina InstallCatalina-$RANDOM_SUFFIX; diskutil rename InstallBigSur InstallBigSur-$RANDOM_SUFFIX; cd "$HOME/Documents/Programming/Free Geek/MacLand Images/" && echo "RESTORING '$MTB_SOURCE_IMAGE' TO 'MTB-$RANDOM_SUFFIX'" && sudo asr restore --source $MTB_SOURCE_IMAGE --target /Volumes/MTB-$RANDOM_SUFFIX --erase --noprompt; echo "CREATING HIGH SIERRA INSTALLER ON 'InstallHighSierra-$RANDOM_SUFFIX'" && sudo '/Volumes/Install macOS High Sierra/Install macOS High Sierra.app/Contents/Resources/createinstallmedia' --volume /Volumes/InstallHighSierra-$RANDOM_SUFFIX --nointeraction; echo "CREATING CATALINA INSTALLER ON 'InstallCatalina-$RANDOM_SUFFIX'" && sudo '/Volumes/Install macOS Catalina/Install macOS Catalina.app/Contents/Resources/createinstallmedia' --volume /Volumes/InstallCatalina-$RANDOM_SUFFIX --nointeraction; echo "CREATING BIG SUR INSTALLER ON 'InstallBigSur-$RANDOM_SUFFIX'" && sudo '/Volumes/Install macOS Big Sur/Install macOS Big Sur.app/Contents/Resources/createinstallmedia' --volume /Volumes/InstallBigSur-$RANDOM_SUFFIX --nointeraction
30 |
31 |
32 | # Command to Create Production OS Images
33 |
34 | cd "$HOME/Documents/Programming/Free Geek/MacLand Images/Restore Images/" && read "?'High Sierra HD' Device Name (Probably $(diskutil list | grep "High Sierra HD" | head -1 | awk '{ print $NF }' | rev | cut -c 3- | rev)): " DEVICE_NAME && diskutil unmountDisk $DEVICE_NAME || diskutil unmountDisk force $DEVICE_NAME; sudo hdiutil create "HighSierra-ProductionRestore-$(date +%Y%m%d).dmg" -srcdevice $DEVICE_NAME && sudo asr imagescan --source "HighSierra-ProductionRestore-$(date +%Y%m%d).dmg"
35 |
36 | cd "$HOME/Documents/Programming/Free Geek/MacLand Images/Restore Images/" && read "?'Catalina HD' Device Name (Probably $(diskutil info $(diskutil list | grep "Catalina HD" | head -1 | awk '{ print $NF }') | grep "APFS Physical Store" | awk '{ print $NF }' | rev | cut -c 3- | rev)): " DEVICE_NAME && diskutil unmountDisk $DEVICE_NAME || diskutil unmountDisk force $DEVICE_NAME; sudo hdiutil create "Catalina-ProductionRestore-$(date +%Y%m%d).dmg" -srcdevice $DEVICE_NAME && sudo asr imagescan --source "Catalina-ProductionRestore-$(date +%Y%m%d).dmg"
37 |
38 |
39 | # Commands to Create Catalina Restore Boot
40 |
41 | cd "$HOME/Documents/Programming/Free Geek/MacLand Images/" && read "?'Catalina Restore Boot' Device Name (Probably $(diskutil list | grep "Catalina Restore Boot" | head -1 | awk '{ print $NF }' | rev | cut -c 3- | rev)): " DEVICE_NAME && diskutil unmountDisk $DEVICE_NAME || diskutil unmountDisk force $DEVICE_NAME; sudo hdiutil create "FreeGeek-CatalinaRestoreBoot-Catalina-$(date +%Y%m%d).dmg" -srcdevice $DEVICE_NAME && sudo asr imagescan --source "FreeGeek-CatalinaRestoreBoot-Catalina-$(date +%Y%m%d).dmg"
42 |
43 |
44 | # Commands to Restore Mac Test Boot and Catalina Restore Boot
45 |
46 | # DO THESE RESTORES IN SERIES FOR EACH DRIVE BECAUSE CATALINA
47 | # WILL OFTEN TIMEOUT IF DONE IN PARALLEL ON THE SAME DRIVE
48 |
49 | # VOLUMES ARE SUFFIXED WITH RANDOM NUMBER FOR LATER IDENTIFICATION
50 | # SO MULTIPLE DRIVES CAN GET STARTED AND RUN AT ONCE WITHOUT CONFLICTING
51 |
52 | MTB_IMAGE_DATE=20210215; CRB_IMAGE_DATE=20210215; MTB_SOURCE_IMAGE=FreeGeek-MacTestBoot-HighSierra-$MTB_IMAGE_DATE.dmg; CRB_SOURCE_IMAGE=FreeGeek-CatalinaRestoreBoot-Catalina-$CRB_IMAGE_DATE.dmg; RANDOM_SUFFIX=$RANDOM; diskutil rename MTB MTB-$RANDOM_SUFFIX; diskutil rename CRB CRB-$RANDOM_SUFFIX; cd "$HOME/Documents/Programming/Free Geek/MacLand Images/Previous Test Boot Archive/" && echo "RESTORING '$MTB_SOURCE_IMAGE' TO 'MTB-$RANDOM_SUFFIX'" && sudo asr restore --source $MTB_SOURCE_IMAGE --target /Volumes/MTB-$RANDOM_SUFFIX --erase --noprompt; echo "RESTORING '$CRB_SOURCE_IMAGE' TO 'CRB-$RANDOM_SUFFIX'" && sudo asr restore --source "Legacy Mac Test Boot/$CRB_SOURCE_IMAGE" --target /Volumes/CRB-$RANDOM_SUFFIX --erase --noprompt
53 |
54 |
55 | # LEGACY MAC TEST BOOT RESTORE:
56 |
57 | LMTB_IMAGE_DATE=121019; LMTB_SOURCE_IMAGE=FreeGeek-MacTestBoot-ElCapitan-$LMTB_IMAGE_DATE.dmg; RANDOM_SUFFIX=$RANDOM; diskutil rename LMTB LMTB-$RANDOM_SUFFIX && cd "$HOME/Documents/Programming/Free Geek/MacLand Images/Previous Test Boot Archive/Legacy Mac Test Boot" && echo "RESTORING '$LMTB_SOURCE_IMAGE' TO 'LMTB-$RANDOM_SUFFIX'"; sudo asr restore --source $LMTB_SOURCE_IMAGE --target /Volumes/LMTB-$RANDOM_SUFFIX --erase --noprompt
58 |
59 |
60 | # LEGACY APFS RESTORE BOOT RESTORE:
61 |
62 | ARB_IMAGE_DATE=121019; ARB_SOURCE_IMAGE=FreeGeek-APFSRestoreBoot-HighSierra-$ARB_IMAGE_DATE.dmg; RANDOM_SUFFIX=$RANDOM; diskutil rename ARB ARB-$RANDOM_SUFFIX && cd "$HOME/Documents/Programming/Free Geek/MacLand Images/Previous Test Boot Archive/Legacy Mac Test Boot" && echo "RESTORING '$ARB_SOURCE_IMAGE' TO 'ARB-$RANDOM_SUFFIX'"; sudo asr restore --source $ARB_SOURCE_IMAGE --target /Volumes/ARB-$RANDOM_SUFFIX --erase --noprompt
63 |
--------------------------------------------------------------------------------
/Build Tools/update-mac-drives.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # shellcheck enable=add-default-case,avoid-nullary-conditions,check-unassigned-uppercase,deprecate-which,quote-safe-variables,require-double-brackets
3 |
4 | #
5 | # MIT License
6 | #
7 | # Copyright (c) 2022 Free Geek
8 | #
9 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"),
10 | # to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
11 | # and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
12 | #
13 | # The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
14 | #
15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
17 | # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
18 | #
19 |
20 | PATH='/usr/bin:/bin:/usr/sbin:/sbin:/usr/libexec' # Add "/usr/libexec" to PATH for easy access to PlistBuddy.
21 |
22 | PROJECT_DIR="$(cd "${BASH_SOURCE[0]%/*}" &> /dev/null && pwd -P)/.."
23 | readonly PROJECT_DIR
24 |
25 | if (( ${EUID:-$(id -u)} != 0 )); then
26 | >&2 echo 'ERROR: This script must be run as root (to be able to run the "createinstallmedia" and "asr" commands).'
27 | afplay /System/Library/Sounds/Basso.aiff
28 | exit 1
29 | fi
30 |
31 |
32 | declare -a installer_names_to_update=( 'High Sierra' 'Mojave' 'Catalina' 'Big Sur' 'Monterey' 'Ventura' 'Sonoma beta' )
33 |
34 | for this_installer_name_to_update in "${installer_names_to_update[@]}"; do
35 | found_connected_installer_for_os=false
36 | # Suppress ShellCheck suggestion to use "find" instead of "ls" since we need "ls -t" to sort by modification date to easily get a single result of the newest installer, and this path will never contain non-alphanumeric characters.
37 | # shellcheck disable=SC2012
38 | installer_dmg_path="$(ls -t "${PROJECT_DIR}/../../MacLand Images/macOS Installers/Install macOS ${this_installer_name_to_update}"*'.dmg' | head -1)"
39 | echo -e "\nMounting Installer DMG \"${installer_dmg_path##*/}\"..."
40 | installer_source_volume="$(hdiutil attach "${installer_dmg_path}" -nobrowse -readonly -plist 2> /dev/null | xmllint --xpath 'string(//string[starts-with(text(), "/Volumes/")])' - 2> /dev/null)"
41 | if [[ -d "${installer_source_volume}" ]]; then
42 | installer_source_version="$(PlistBuddy -c 'Print :CFBundleVersion' "${installer_source_volume}/Install macOS ${this_installer_name_to_update}.app/Contents/version.plist")"
43 |
44 | echo "Mounted Installer DMG at \"${installer_source_volume}\" & Updating Connected Installers..."
45 | for this_os_installer_volume in "/Volumes/Install macOS ${this_installer_name_to_update}"*; do
46 | if [[ -d "${this_os_installer_volume}" && "${this_os_installer_volume}" != "${installer_source_volume}" ]]; then
47 | found_connected_installer_for_os=true
48 | this_os_installer_version="$(PlistBuddy -c 'Print :CFBundleVersion' "${this_os_installer_volume}/Install macOS ${this_installer_name_to_update}.app/Contents/version.plist")"
49 | if [[ "${this_os_installer_version}" != "${installer_source_version}" ]]; then
50 | echo "Updating Connected Installer at \"${this_os_installer_volume}\"..."
51 | "${installer_source_volume}/Install macOS ${this_installer_name_to_update}.app/Contents/Resources/createinstallmedia" --volume "${this_os_installer_volume}" --nointeraction &
52 | sleep 10 # Sleep a bit before starting the next "createinstallmedia" process since I've seen them fail consistently with "Couldn't find InstallInfo.plist" and "The bless of the installer disk failed." when two Montery "createinstallmedia" processes were started at the same time.
53 | else
54 | echo "Connected Installer at \"${this_os_installer_volume}\" Already Up-to-Date"
55 | fi
56 | fi
57 | done
58 |
59 | wait # Wait for child "createinstallmedia" processes for this OS version to finish before moving to the next to not tax each drive too much by writing to multiple partitions at the same time which tends to cause more failures.
60 |
61 | some_update_failed=false
62 | if ! $found_connected_installer_for_os; then
63 | echo "No Connected ${this_installer_name_to_update} Installers Found"
64 | else
65 | for this_os_installer_volume in "/Volumes/Install macOS ${this_installer_name_to_update}"*; do
66 | if [[ -d "${this_os_installer_volume}" && "${this_os_installer_volume}" != "${installer_source_volume}" ]]; then
67 | this_os_installer_version="$(PlistBuddy -c 'Print :CFBundleVersion' "${this_os_installer_volume}/Install macOS ${this_installer_name_to_update}.app/Contents/version.plist")"
68 | if [[ "${this_os_installer_version}" != "${installer_source_version}" ]]; then
69 | >&2 echo "ERROR: Failed to update connected installer at \"${this_os_installer_volume}\"."
70 | some_update_failed=true
71 | else
72 | echo "Unmounting Connected Installer at \"${this_os_installer_volume}\"..."
73 | diskutil unmount "${this_os_installer_volume}" &> /dev/null || diskutil unmount force "${this_os_installer_volume}" &> /dev/null || >&2 echo "ERROR: Failed to unmount connected installer at \"${this_os_installer_volume}\"."
74 | fi
75 | fi
76 | done
77 | fi
78 |
79 | echo "Unmounting Installer DMG at \"${installer_source_volume}\"..."
80 | hdiutil detach "${installer_source_volume}" &> /dev/null || hdiutil detach "${installer_source_volume}" -force &> /dev/null || >&2 echo "ERROR: Failed to unmount DMG at \"${installer_source_volume}\"."
81 |
82 | if $some_update_failed; then
83 | afplay /System/Library/Sounds/Basso.aiff
84 | exit 2
85 | fi
86 | else
87 | >&2 echo "ERROR: \"${this_installer_name_to_update}\" Installer DMG was not found or mounted."
88 | afplay /System/Library/Sounds/Basso.aiff
89 | exit 3
90 | fi
91 | done
92 |
93 |
94 | found_connected_mtb=false
95 | # Suppress ShellCheck suggestion to use "find" instead of "ls" since we need "ls -t" to sort by modification date to easily get a single result of the newest MTB image, and this path will never contain non-alphanumeric characters.
96 | # shellcheck disable=SC2012
97 | mtb_dmg_path="$(ls -t "${PROJECT_DIR}/../../MacLand Images/FreeGeek-MacTestBoot-"*'.dmg' | head -1)"
98 | echo -e "\nMounting MTB Source DMG \"${mtb_dmg_path##*/}\" to Get Version..."
99 | mtb_source_volume="$(hdiutil attach "${mtb_dmg_path}" -nobrowse -readonly -plist 2> /dev/null | xmllint --xpath 'string(//string[starts-with(text(), "/Volumes/")])' - 2> /dev/null)"
100 | if [[ -d "${mtb_source_volume}" ]]; then
101 | mtb_source_version="$(< "${mtb_source_volume}/private/var/root/.mtbVersion")"
102 | echo "MTB Source Version: ${mtb_source_version}"
103 | echo "Unmounting MTB Source DMG at \"${mtb_source_volume}\"..."
104 | hdiutil detach "${mtb_source_volume}" &> /dev/null || hdiutil detach "${mtb_source_volume}" -force &> /dev/null || >&2 echo "ERROR: Failed to unmount DMG at \"${mtb_source_volume}\"."
105 |
106 | echo -e '\nUpdating Connected MTBs...'
107 | for this_mtb_volume in '/Volumes/Mac Test Boot'*; do
108 | if [[ -d "${this_mtb_volume}" ]]; then
109 | found_connected_mtb=true
110 | this_connected_mtb_version="$(< "${this_mtb_volume}/private/var/root/.mtbVersion")"
111 | if [[ "${this_connected_mtb_version}" != "${mtb_source_version}" ]]; then
112 | echo "Updating Connected MTB at \"${this_mtb_volume}\"..."
113 | asr restore --source "${mtb_dmg_path}" --target "${this_mtb_volume}" --erase --noprompt &
114 | else
115 | echo "Connected MTB at \"${this_mtb_volume}\" Already Up-to-Date"
116 | fi
117 | fi
118 | done
119 |
120 | wait # Wait for child "asr" processes for the MTBs to finish before moving on.
121 |
122 | some_mtb_failed=false
123 | if ! $found_connected_mtb; then
124 | echo 'No Connected MTBs Found'
125 | else
126 | for this_mtb_volume in '/Volumes/Mac Test Boot'*; do
127 | if [[ -d "${this_mtb_volume}" ]]; then
128 | this_connected_mtb_version="$(< "${this_mtb_volume}/private/var/root/.mtbVersion")"
129 | if [[ "${this_connected_mtb_version}" != "${mtb_source_version}" ]]; then
130 | >&2 echo "ERROR: Failed to update connected MTB at \"${this_mtb_volume}\"."
131 | some_mtb_failed=true
132 | else
133 | echo "Unmounting Connected MTB at \"${this_mtb_volume}\"..."
134 | diskutil unmount "${this_mtb_volume}" &> /dev/null || diskutil unmount force "${this_mtb_volume}" &> /dev/null || >&2 echo "ERROR: Failed to unmount connected MTB at \"${this_mtb_volume}\"."
135 | fi
136 | fi
137 | done
138 | fi
139 |
140 | if $some_mtb_failed; then
141 | afplay /System/Library/Sounds/Basso.aiff
142 | exit 5
143 | fi
144 | else
145 | >&2 echo "ERROR: \"${mtb_dmg_path}\" MTB source DMG was not found or mounted."
146 | afplay /System/Library/Sounds/Basso.aiff
147 | exit 4
148 | fi
149 |
150 | echo -e '\nUpdating Connected fgMIBs...'
151 | bash "${PROJECT_DIR}/fgMIB Resources/Build Tools/copy-fgMIB-resources-to-drives.sh"
152 |
153 | afplay /System/Library/Sounds/Glass.aiff
154 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Free Geek
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 |
--------------------------------------------------------------------------------
/Other Scripts/every_intel_mac_marketing_model_name_with_grouped_model_ids_and_serial_config_codes.txt:
--------------------------------------------------------------------------------
1 | iMac (17-inch, Early 2006):iMac4,1:U2N:U2R:V4M:V4N:V4U:V66:VGB:VGZ:VH1:VHP:VV4:VV6:
2 | iMac (17-inch, Late 2006 CD):iMac5,2:
3 | iMac (17-inch, Late 2006):iMac5,1:AC1:VUX:VUY:WAR:WRR:WRW:WV8:WVR:X1A:X1W:X2W:X6Q:X9F:X9Y:XLF:Y3V:Y3W:Y3X:Y6K:Y94:Y97:YAG:YLJ:
4 | iMac (17-inch, Mid 2006):iMac4,2:
5 | iMac (20-inch, Early 2006):iMac4,1:U2P:U2S:V4P:V4Q:V4R:V67:VGC:VGM:VH0:VH2:VW4:VX0:WXN:X0U:
6 | iMac (20-inch, Early 2008):iMac8,1:28B:2PN:2PR:3FF:3FG:3SZ:5A8:5J0:6F9:8R2:8R3:ZE2:ZE3:ZE5:ZE6:
7 | iMac (20-inch, Early 2009):iMac9,1:0TF:0TH:6X0:8M5:8TS:8TT:9EX:9LN:
8 | iMac (20-inch, Late 2006):iMac5,1:VUV:VUW:WRS:WRX:WSD:X0E:X29:X6S:X9E:X9G:XA4:XCR:XCY:Y3R:Y3U:Y9B:YAE:YDW:
9 | iMac (20-inch, Mid 2007):iMac7,1:02X:09Q:0PQ:0PR:0PT:0U1:1NU:1NV:3PB:X85:X86:X87:X88:Z58:Z9G:ZEG:ZFD:
10 | iMac (20-inch, Mid 2009):iMac9,1:6MH:6MJ:9TH:BAH:DMV:DWY:E86:FUN:FXN:GM9:H1S:HS6:HS7:HT6:HUE:
11 | iMac (21.5-inch, 2017):iMac18,1:
12 | iMac (21.5-inch, Early 2013):iMac13,3:
13 | iMac (21.5-inch, Late 2009):iMac10,1:5PC:5PK:B9S:B9U:CY8:DMW:DMX:DWR:DWU:E8D:E8E:E8F:F0G:F0H:FQH:FU1:H9K:HDF:
14 | iMac (21.5-inch, Late 2011):iMac12,1:DKL9:DKLH:DPNK:DPNW:
15 | iMac (21.5-inch, Late 2012):iMac13,1:
16 | iMac (21.5-inch, Late 2013):iMac14,1:iMac14,3:
17 | iMac (21.5-inch, Late 2015):iMac16,1:iMac16,2:GF1J:GF1K:GF1L:GF1M:GG77:GG79:GG7D:GG7G:H0N6:H0P6:H1DX:H1DY:H1F1:H1F2:H1WR:H25M:H2KW:H8KX:HHMG:HQ9T:HQ9V:HQ9W:HYGQ:J0DG:J0DH:J0DJ:
18 | iMac (21.5-inch, Mid 2010):iMac11,2:
19 | iMac (21.5-inch, Mid 2011):iMac12,1:DHJF:DHJN:DHJR:DHJT:DL8M:DL8N:DMP0:DNWY:DPM0:DPNT:DWTP:DWTQ:F611:
20 | iMac (21.5-inch, Mid 2014):iMac14,4:
21 | iMac (24-inch, Early 2008):iMac8,1:0KM:0N4:1LW:28A:2E4:2NX:2PT:39S:3F9:3FH:3GS:3NX:5J1:5U6:6J3:6J6:6ZC:ZE4:ZE7:
22 | iMac (24-inch, Early 2009):iMac9,1:0TG:0TJ:0TL:0TM:250:259:6X1:6X2:6X3:8M6:8XH:9ET:9F3:9LP:9LQ:9LR:9LS:E1B:
23 | iMac (24-inch, Late 2006):iMac6,1:
24 | iMac (24-inch, Mid 2007):iMac7,1:0PL:0PM:0PN:0PP:0PU:1NW:1SC:2CB:3PA:X89:X8A:Z59:Z9F:ZCR:ZCT:ZCV:ZCW:ZEF:ZGH:ZGP:
25 | iMac (27-inch, Late 2009):iMac10,1:iMac11,1:5PE:5PJ:5PM:5RU:CYB:CYC:D4V:DMY:DMZ:DWZ:E1J:F0J:F0K:GRP:H9L:H9N:H9P:H9R:
26 | iMac (27-inch, Late 2012):iMac13,2:
27 | iMac (27-inch, Late 2013):iMac14,2:
28 | iMac (27-inch, Mid 2010):iMac11,3:
29 | iMac (27-inch, Mid 2011):iMac12,2:
30 | iMac (Retina 4K, 21.5-inch, 2017):iMac18,2:
31 | iMac (Retina 4K, 21.5-inch, 2019):iMac19,2:
32 | iMac (Retina 4K, 21.5-inch, Late 2015):iMac16,2:GG78:GG7C:GG7F:GG7H:H0KF:H0P7:H15R:H1F3:H1F5:H1F7:H1F8:H1F9:H25N:H28H:H3RJ:H8KY:H8L0:H8L1:H8L2:H8L3:HLWV:
33 | iMac (Retina 5K, 27-inch, 2017):iMac18,3:
34 | iMac (Retina 5K, 27-inch, 2019):iMac19,1:
35 | iMac (Retina 5K, 27-inch, 2020):iMac20,1:iMac20,2:
36 | iMac (Retina 5K, 27-inch, Late 2014):iMac15,1:FY11:FY14:FY68:FY6F:GCTM:GDQY:GDR3:GDR4:GDR5:GDR6:GDR7:GDR8:GDR9:GDRC:GFFQ:GJDM:GJDN:GJDP:GJDQ:GPJN:GV7V:H5DN:H682:
37 | iMac (Retina 5K, 27-inch, Late 2015):iMac17,1:
38 | iMac (Retina 5K, 27-inch, Mid 2015):iMac15,1:FY10:FY13:FY67:FY6D:GL1Q:GL1R:GL1T:GL1V:GL1W:
39 | iMac Pro (2017):iMacPro1,1:
40 | Mac mini (2018):Macmini8,1:
41 | Mac mini (Early 2006):Macmini1,1:U35:U36:U38:U39:VJN:VLK:VS5:VS7:VU2:VU4:WBZ:WCU:WEN:
42 | Mac mini (Early 2009):Macmini3,1:19X:19Y:1BU:1BV:8NC:92G:9RR:9RS:AFR:BAV:
43 | Mac mini (Late 2006):Macmini1,1:W0A:W0B:W0C:W0D:WKN:X1X:X1Y:X1Z:X20:XAS:Y9E:
44 | Mac mini (Late 2009):Macmini3,1:306:307:9G5:9G6:9G7:9G8:AFK:B9X:CS6:DMG:DMH:F6J:
45 | Mac mini (Late 2012):Macmini6,1:Macmini6,2:DWYL:DWYM:DY3G:DY3H:F9RK:F9RL:F9RM:F9VV:F9VW:F9W0:F9W1:F9W2:FD9G:FD9H:FD9J:FD9K:FDWK:FGML:FRFP:FW56:FW57:G430:
46 | Mac mini (Late 2014):Macmini7,1:
47 | Mac mini (Mid 2007):Macmini2,1:
48 | Mac mini (Mid 2010):Macmini4,1:DD6H:DD6L:DDQ9:DDVN:DFDK:
49 | Mac mini (Mid 2011):Macmini5,1:Macmini5,2:
50 | Mac mini Server (Late 2012):Macmini6,2:DWYN:DY3J:F9VY:F9W3:FC08:FCCW:FP14:FP39:
51 | Mac mini Server (Mid 2010):Macmini4,1:DD6K:DD6N:DDJF:
52 | Mac mini Server (Mid 2011):Macmini5,3:
53 | Mac Pro (2019):MacPro7,1:K7GD:K7GF:NYGV:P7QJ:P7QK:P7QL:P7QM:P7QN:P7QP:PLXV:PLXW:PLXX:PLXY:
54 | Mac Pro (Early 2008):MacPro3,1:
55 | Mac Pro (Early 2009):MacPro4,1:20G:20H:4PC:4PD:7BF:8MC:8PZ:8Q0:8TR:8TU:8XG:8XL:93H:9EU:9EV:9MC:9MD:9MG:9MJ:9MK:9ML:9QK:ANS:BXD:BXE:BXT:CZ2:CZ3:CZ4:E1C:E1D:E1E:EAA:EYX:EYY:F6H:GYH:
56 | Mac Pro (Late 2013):MacPro6,1:
57 | Mac Pro (Mid 2010):MacPro5,1:EUE:EUF:EUG:EUH:GWR:GY5:GZH:GZJ:GZK:GZL:GZM:H0X:H2N:H2P:H97:H99:HF7:HF8:HF9:HFA:HFC:HFD:HFF:HFG:HFJ:HFK:HFL:HFN:HG1:HG3:HP9:HPA:
58 | Mac Pro (Mid 2012):MacPro5,1:F4MC:F4MD:F4MG:F4MH:F4YY:F500:F648:F649:F64C:F64D:F64F:F6T9:F6TC:F6TD:F6TF:F6TG:
59 | Mac Pro (Rack, 2019):MacPro7,1:N5RH:N5RN:P7QQ:P7QR:P7QT:P7QV:PNTN:PNTP:PNTQ:PP3Y:
60 | Mac Pro Server (Mid 2010):MacPro5,1:HPV:HPW:HPY:
61 | Mac Pro Server (Mid 2012):MacPro5,1:F4MF:F4MJ:F501:
62 | Mac Pro:MacPro1,1:MacPro2,1:
63 | MacBook (13-inch):MacBook1,1:
64 | MacBook (13-inch, Aluminum, Late 2008):MacBook5,1:
65 | MacBook (13-inch, Early 2008):MacBook4,1:0P0:0P1:0P2:0P4:0P5:0P6:1LX:1PX:1Q2:1Q7:1QA:1QB:1QE:1ZY:27H:27J:28C:28D:28E:385:3N9:3NA:3ND:3NE:3NF:3X6:47Z:4R7:4R8:
66 | MacBook (13-inch, Early 2009):MacBook5,2:4R1:4R2:4R3:79D:79E:79F:7A2:85D:88J:8CP:8SJ:93K:
67 | MacBook (13-inch, Late 2006):MacBook2,1:WGK:WGL:WGM:WGN:WGP:WGQ:WGS:WGT:WGU:WVN:X6G:X6H:X6J:X6K:X6L:X7X:X97:X98:XAR:XAT:XC5:XDN:XDR:XDS:XDT:XDU:XDV:XDW:XDX:XDY:XDZ:XE0:XE1:XE2:XE3:XHB:XHC:XKT:XMF:Y6L:Y6M:Y9A:YCU:
68 | MacBook (13-inch, Late 2007):MacBook3,1:
69 | MacBook (13-inch, Late 2008):MacBook4,1:3VY:5AQ:5HS:5HU:67C:6ES:6HY:6LL:6LM:6M1:6V9:6YP:7XD:
70 | MacBook (13-inch, Late 2009):MacBook6,1:
71 | MacBook (13-inch, Mid 2007):MacBook2,1:YA2:YA3:YA4:YA5:YA6:YA7:YA8:YA9:YJJ:YJK:YJL:YJM:YJN:YQ7:YQ8:YRG:YRH:YRJ:YRK:YSH:YSJ:YSK:YSL:YSM:YTK:YTL:YV8:YX1:YX2:YX4:YX5:YXZ:YY1:YYW:Z5V:Z5W:Z5X:Z5Y:Z5Z:Z60:Z88:ZA8:ZA9:ZAP:ZAQ:ZAS:ZAU:ZAV:ZAW:ZAX:ZAY:ZAZ:ZB0:ZB1:ZB2:ZB7:ZB8:ZB9:ZBA:ZBB:ZBE:ZBF:ZBG:ZBH:ZBJ:ZBK:ZCN:
72 | MacBook (13-inch, Mid 2009):MacBook5,2:9GU:9GV:A1W:A1X:A1Y:A9P:A9Q:A9Y:ABW:ASC:
73 | MacBook (13-inch, Mid 2010):MacBook7,1:
74 | MacBook (Retina, 12-inch, 2017):MacBook10,1:
75 | MacBook (Retina, 12-inch, Early 2015):MacBook8,1:
76 | MacBook (Retina, 12-inch, Early 2016):MacBook9,1:
77 | MacBook Air (11-inch, Early 2014):MacBookAir6,1:FM72:G083:G084:G2CF:G2GH:G2GJ:G2PY:G2Q0:G4FY:G4H0:G4H4:G4HK:G4HM:G58J:G5RK:G5RL:G5RM:G6D3:GLK9:GP4N:GP4P:
78 | MacBook Air (11-inch, Early 2015):MacBookAir7,1:
79 | MacBook Air (11-inch, Late 2010):MacBookAir3,1:
80 | MacBook Air (11-inch, Mid 2011):MacBookAir4,1:
81 | MacBook Air (11-inch, Mid 2012):MacBookAir5,1:
82 | MacBook Air (11-inch, Mid 2013):MacBookAir6,1:F5N7:F5N8:F5YV:F5YW:FH51:FH52:FKYN:FKYP:FLCF:FMR5:FMR6:FMR9:FMRC:FMRD:FMRF:FMRG:FMRM:FMRN:FN5M:FN7F:FP2N:FP3C:FQLG:FT30:
83 | MacBook Air (13-inch, 2017):MacBookAir7,2:J1WK:J1WL:J1WM:J1WT:J1WV:J8N7:J8XG:J8XH:J9HX:J9TN:J9TP:J9TQ:JC9H:JCD6:JFLY:JKHD:JKHF:LQ07:LQF1:MFWJ:
84 | MacBook Air (13-inch, Early 2014):MacBookAir6,2:G085:G086:G2CC:G2CD:G2GK:G2GL:G2GM:G2GN:G356:G4H1:G4H2:G4H3:G4HN:G4HP:G58K:G5RN:G5RP:G5RQ:G6D4:G6D5:G829:G8J1:GLK7:GLK8:GP4L:GP4M:
85 | MacBook Air (13-inch, Early 2015):MacBookAir7,2:G940:G941:G942:G943:G944:GKJT:GKJV:GL20:GL21:GL22:GL23:GL24:GL25:GLCN:GLCP:GM14:GM15:GM38:GM6M:GM9G:GMC3:GMD3:GN8C:GNJJ:GNKM:H3QD:H3QF:H3QJ:H3QK:H569:H8VT:H8VV:H8VW:H8VX:HD7X:HD80:HD98:HDV4:HDV5:HDV6:HF4F:HF4H:HF9N:J6VL:
86 | MacBook Air (13-inch, Late 2010):MacBookAir3,2:
87 | MacBook Air (13-inch, Mid 2011):MacBookAir4,2:
88 | MacBook Air (13-inch, Mid 2012):MacBookAir5,2:
89 | MacBook Air (13-inch, Mid 2013):MacBookAir6,2:F5V7:F5V8:F6T5:F6T6:FH53:FKYQ:FKYR:FLCG:FM23:FM3Y:FM74:FMR7:FMR8:FMRH:FMRJ:FMRK:FMRL:FMRV:FMRW:FMRY:FN3Y:FN40:FN7G:FP2P:FQL9:FQLC:FQLD:FQLF:G6PM:
90 | MacBook Air (Late 2008):MacBookAir2,1:22D:22E:5L9:5LA:5TX:5U1:5U7:60R:62W:63V:63W:6JN:
91 | MacBook Air (Mid 2009):MacBookAir2,1:9A5:9A6:9A7:9A8:
92 | MacBook Air (Original):MacBookAir1,1:
93 | MacBook Air (Retina, 13-inch, 2018):MacBookAir8,1:
94 | MacBook Air (Retina, 13-inch, 2019):MacBookAir8,2:
95 | MacBook Air (Retina, 13-inch, 2020):MacBookAir9,1:
96 | MacBook Pro (13-inch, 2016, Four Thunderbolt 3 Ports):MacBookPro13,2:
97 | MacBook Pro (13-inch, 2016, Two Thunderbolt 3 ports):MacBookPro13,1:
98 | MacBook Pro (13-inch, 2017, Four Thunderbolt 3 Ports):MacBookPro14,2:
99 | MacBook Pro (13-inch, 2017, Two Thunderbolt 3 ports):MacBookPro14,1:
100 | MacBook Pro (13-inch, 2018, Four Thunderbolt 3 Ports):MacBookPro15,2:JHC8:JHC9:JHCC:JHCD:JHCF:JHD2:JHD3:JHD4:JHD5:KK98:KK99:KK9C:KQ1X:KQ1Y:KQ20:KQ21:KQ22:KQ23:KQ24:KQ25:KQ26:KQ27:L42X:L4FC:L4FD:L4FF:L4FG:L4FJ:L4JT:L7GD:LK8C:
101 | MacBook Pro (13-inch, 2019, Four Thunderbolt 3 ports):MacBookPro15,2:LVDC:LVDD:LVDF:LVDG:LVDH:LVDL:LVDM:LVDN:LVDP:MV9K:MV9R:N5T5:NCLV:NCLW:NCLX:NCLY:NCM0:NCM1:NCM2:NQM8:P4G1:P4G2:
102 | MacBook Pro (13-inch, 2019, Two Thunderbolt 3 ports):MacBookPro15,4:
103 | MacBook Pro (13-inch, 2020, Four Thunderbolt 3 ports):MacBookPro16,2:
104 | MacBook Pro (13-inch, 2020, Two Thunderbolt 3 ports):MacBookPro16,3:
105 | MacBook Pro (13-inch, Early 2011):MacBookPro8,1:DH2G:DH2H:DH2L:DH2M:DLN5:DLN6:DM75:DMLF:DMLH:DMLJ:DNCM:DNGD:DNKP:DNKQ:DNTK:DNVY:DR7W:DRJ7:DRJ9:DRJJ:DRJK:DRW1:DRW2:DRW7:DT4G:DT4H:DT60:DT61:DT62:DT63:DT64:DT65:DT66:DT67:ST61:
106 | MacBook Pro (13-inch, Late 2011):MacBookPro8,1:DV13:DV14:DV16:DV17:DVHJ:DVHK:DVHP:DVHQ:DW13:DY1J:DY1K:DY5T:DY5V:DY6C:DY77:DYL0:DYL1:DYL2:F298:F299:
107 | MacBook Pro (13-inch, Mid 2009):MacBookPro5,5:
108 | MacBook Pro (13-inch, Mid 2010):MacBookPro7,1:
109 | MacBook Pro (13-inch, Mid 2012):MacBookPro9,2:
110 | MacBook Pro (15-inch, 2.4/2.2GHz):MacBookPro3,1:02V:0LQ:0LZ:0M0:0PA:0S3:0S6:1CY:1CZ:2QU:2QV:X91:X92:XAG:XAH:Y9S:Y9T:YAL:YAM:YKX:YKY:YKZ:YL0:YQ3:YW5:YW9:YWA:YWD:YYV:YYX:YZ0:Z05:Z09:Z0G:
111 | MacBook Pro (15-inch, 2.53GHz, Mid 2009):MacBookPro5,4:
112 | MacBook Pro (15-inch, 2016):MacBookPro13,3:
113 | MacBook Pro (15-inch, 2017):MacBookPro14,3:
114 | MacBook Pro (15-inch, 2018):MacBookPro15,1:MacBookPro15,3:JG5H:JG5J:JG5K:JG5L:JG5M:JGH5:JGH6:JGH7:JGH8:KGYF:KGYG:KGYH:KQ9Q:KQ9R:KQ9T:KQ9V:KQ9W:KQ9X:KQ9Y:KQC0:KQC1:KQC2:KQC3:KQC4:KQC5:KQC6:KQC7:KQC8:KQC9:KQCC:KQCD:KQCF:KQCG:KQCH:KQCJ:KQCK:KQCL:KQCM:KQCN:KQCP:KQCQ:KQCR:KQCT:KQCV:KQCW:KQCX:KWJ2:L4HW:L4HX:L539:L53D:L7GC:LC8J:LC8K:LC8L:LCM6:MJLR:MJLT:
115 | MacBook Pro (15-inch, 2019):MacBookPro15,1:MacBookPro15,3:LVCF:LVCG:LVCH:LVCJ:LVCK:LVCL:LVDQ:LVDR:LVDT:LVDV:MV9T:MVC0:N5T6:N6KF:N6RJ:NCM3:NCM4:NCM5:NCM6:NQM9:NQMC:NQMD:NQMF:
116 | MacBook Pro (15-inch, Core 2 Duo):MacBookPro2,2:
117 | MacBook Pro (15-inch, Early 2008):MacBookPro4,1:1AJ:1EK:1EM:1JZ:1K0:1SH:1XR:1XW:27N:2AZ:2B0:2CE:2DT:2DX:2MF:2PK:33B:3LY:3LZ:48T:4R5:4R6:YJX:YJY:YJZ:YK0:ZLU:
118 | MacBook Pro (15-inch, Early 2011):MacBookPro8,2:DF8V:DF8X:DF8Y:DF91:DLN7:DLN8:DMC8:DMC9:DMDG:DMDH:DMDJ:DMGG:DMMF:DMMH:DMMJ:DMPG:DMPK:DMPL:DMPM:DMPN:DMPP:DMPQ:DMPR:DMQP:DNC3:DNCN:DNGF:DNH5:DNHY:DNKM:DNKY:DNM4:DNMW:DNRD:DNVK:DRJC:DRJD:DRJF:DRJL:DRJM:DRW3:DRW4:DRWD:DT4J:DT54:DT55:DT56:DT57:DT58:DT59:DT5C:DT5D:DT5F:DT5G:DT5H:DT5J:DT5L:DT68:DT69:DT6C:DT6D:DT6F:DT6G:DT6H:DT6J:DT6K:DT6L:DT6M:DT6R:
119 | MacBook Pro (15-inch, Glossy):MacBookPro1,1:VWW:VWX:VWY:VWZ:W3N:W92:W93:W94:W9F:W9Q:WAG:WAW:WB8:WBE:WBF:WBH:WBJ:WD7:WD8:WD9:WDA:WDB:WDC:WDD:WTS:WW0:WW1:WW2:WW3:
120 | MacBook Pro (15-inch, Late 2008):MacBookPro5,1:
121 | MacBook Pro (15-inch, Late 2011):MacBookPro8,2:DV7L:DV7M:DV7N:DV7P:DVHL:DVHM:DVHR:DW3G:DW3H:DW3J:DW47:DY1L:DY1M:DY1N:DY1P:DY1Q:DY1R:DY1T:DY1V:DY1W:DY1Y:DY20:DY21:DY5K:DY5P:DY5Q:DY5R:DY5Y:DY60:DY7G:DYG6:DYG7:DYK9:DYKC:DYR1:F0K6:F0V2:
122 | MacBook Pro (15-inch, Mid 2009):MacBookPro5,3:
123 | MacBook Pro (15-inch, Mid 2010):MacBookPro6,2:
124 | MacBook Pro (15-inch, Mid 2012):MacBookPro9,1:
125 | MacBook Pro (16-inch, 2019):MacBookPro16,1:MacBookPro16,4:
126 | MacBook Pro (17-inch):MacBookPro1,2:
127 | MacBook Pro (17-inch, 2.4GHz):MacBookPro3,1:027:028:02D:09R:09S:0LR:0ND:0NM:0PD:1CW:1CX:1MF:1MG:2QW:X94:XA9:YAA:YAN:YAP:YNQ:YNS:YNW:YQ4:YQ5:YR2:YRD:YRE:YRF:YWB:YWC:YZ1:YZ2:Z5M:
128 | MacBook Pro (17-inch, Core 2 Duo):MacBookPro2,1:
129 | MacBook Pro (17-inch, Early 2008):MacBookPro4,1:1BY:1ED:1EN:1ER:1K2:1K8:1K9:1KA:1Q3:1SG:2CF:2DY:2DZ:2ED:3DC:3DD:3DE:3DF:3M0:3M4:3M5:YP3:YP4:ZLV:
130 | MacBook Pro (17-inch, Early 2009):MacBookPro5,2:2QP:2QT:776:77A:7AP:7AS:7XQ:7XR:7XS:87K:87L:87M:87N:8FK:8FL:8FM:8FY:8FZ:8G0:
131 | MacBook Pro (17-inch, Early 2011):MacBookPro8,3:DF92:DF93:DLN9:DLNC:DMGH:DMQT:DMQW:DMR2:DMR4:DMR5:DMR7:DMR8:DMR9:DMRC:DNGG:DNKN:DRJG:DRJH:DRJN:DRW5:DRW6:DT5M:DT5N:DT5P:DT5Q:DT5R:DT5T:DT5V:DT5W:DT5Y:DT6N:DT6P:
132 | MacBook Pro (17-inch, Late 2008):MacBookPro4,1:3R8:3R9:4RT:4RW:57J:5U0:634:65A:663:664:666:668:6CT:6JK:
133 | MacBook Pro (17-inch, Late 2011):MacBookPro8,3:AY5W:DV10:DV11:DVHN:DVHV:DVHW:DW48:DY22:DY23:DY24:DY25:DY26:DY5W:DYG8:F13Y:F140:
134 | MacBook Pro (17-inch, Mid 2009):MacBookPro5,2:8YA:8YB:91T:A3M:A3N:A5R:A5W:AF3:AKV:AKW:AMV:AMW:AN1:ANC:AND:ANE:ANF:ANJ:AUU:E6L:
135 | MacBook Pro (17-inch, Mid 2010):MacBookPro6,1:
136 | MacBook Pro (Original):MacBookPro1,1:THV:VGW:VGX:VGY:VJ0:VJ1:VJ2:VJ3:VJ5:VJ6:VJ7:VJM:VMU:VSD:VTZ:VU0:VWA:VWB:VXW:VXX:W2Q:
137 | MacBook Pro (Retina, 13-inch, Early 2013):MacBookPro10,2:FFRP:FFRR:FG1F:FG28:FGM8:FGN5:FGN6:FGPJ:FHCH:FHN0:
138 | MacBook Pro (Retina, 13-inch, Early 2015):MacBookPro12,1:
139 | MacBook Pro (Retina, 13-inch, Late 2012):MacBookPro10,2:DR53:DR54:DR55:DR56:F775:F776:F7YF:F897:F8V6:F8V7:F8V8:F9JT:F9V1:F9VQ:FG7Q:FG7R:FL85:FMLJ:
140 | MacBook Pro (Retina, 13-inch, Late 2013):MacBookPro11,1:FGYY:FH00:FH01:FH02:FH03:FH04:FH05:FRF6:FRF7:FRQF:FT4Q:FT4R:FT4T:FT4V:FTC9:FTCD:FTCH:FTCK:FTCL:FTPH:FTPJ:FTPK:FTT4:FVVW:FVWQ:FWKF:G4N6:G4N7:
141 | MacBook Pro (Retina, 13-inch, Mid 2014):MacBookPro11,1:G3QH:G3QJ:G3QK:G3QL:G3QQ:G3QR:G3QT:G7RD:G7RF:G7YQ:G7YR:G8L0:G96R:G96T:G96V:G96W:G96Y:G970:G971:G972:G9FL:G9FM:G9FN:G9FP:G9FQ:G9FR:GDJM:
142 | MacBook Pro (Retina, 15-inch, Early 2013):MacBookPro10,1:FFT0:FFT1:FFT2:FFT3:FFT4:FG1H:FG1J:FGFH:FGFJ:FGFK:FGFL:FGN7:FGWF:FGWG:FGWH:FHCQ:FHCR:FJ47:FJVJ:FL94:FMLK:FR8D:
143 | MacBook Pro (Retina, 15-inch, Late 2013):MacBookPro11,2:MacBookPro11,3:FD56:FD57:FD58:FD59:FR1M:FRDM:FRG2:FRG3:FRQH:FRQJ:FRQK:FRQL:FT4P:FTK0:FTK1:FTPL:FTPM:FTPN:FTPP:FTPQ:FTPR:FTPT:FTPV:FTPW:FTPY:FTTJ:FVN4:FVYN:FWFY:FWHW:FWKK:FWKL:G4JQ:G5HL:
144 | MacBook Pro (Retina, 15-inch, Mid 2014):MacBookPro11,2:MacBookPro11,3:G3QC:G3QD:G3QG:G3QN:G3QP:G85Y:G86P:G86Q:G86R:G8F4:G8J7:G8L1:G96K:G96L:G96M:G96N:G96P:G96Q:G973:G974:G9FT:G9JN:G9L6:G9L7:G9L8:G9L9:GDPP:ZORD:
145 | MacBook Pro (Retina, 15-inch, Mid 2015):MacBookPro11,4:MacBookPro11,5:
146 | MacBook Pro (Retina, Mid 2012):MacBookPro10,1:DKQ1:DKQ2:DKQ4:DKQ5:F51R:F5Y2:F69W:F69Y:F6DN:F6F3:F6L9:F8JY:F96W:F9F1:F9F2:FCQ3:
147 | Xserve (Early 2008):Xserve2,1:
148 | Xserve (Early 2009):Xserve3,1:
149 | Xserve (Late 2006):Xserve1,1:
150 |
--------------------------------------------------------------------------------
/Other Scripts/generate_csreq_hex_for_tcc_db.jxa:
--------------------------------------------------------------------------------
1 | #!/usr/bin/osascript -l JavaScript
2 |
3 | //
4 | // Created by Pico Mitchell on 8/24/22.
5 | // For MacLand @ Free Geek
6 | //
7 | // MIT License
8 | //
9 | // Copyright (c) 2022 Free Geek
10 | //
11 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"),
12 | // to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
13 | // and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
14 | //
15 | // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
16 | //
17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
19 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20 | //
21 |
22 |
23 | // Explanation of Code Signing Requirements: https://developer.apple.com/documentation/technotes/tn3127-inside-code-signing-requirements
24 |
25 | // ABOUT CSREQS *HEX* STRING DIFFERENCES AND THE PURPOSE OF THIS SCRIPT:
26 |
27 | // Hexadecimal string representations of Code Signing Requirements data are used when setting TCC permissions directly in the global and user TCC.db files like is done in "fg-install-os.sh" and "Free Geek Setup.applescript".
28 | // In my testing, it seems that including the CSReq hex strings in the TCC.db is not actually a necessary since macOS will fill them in after the app specified by the Bundle ID is launched for the first time.
29 | // But, that would mean that the first app to launch with the specified Bundle ID would have its CSReqs analyzed and the TCC permissions would then get locked to that apps CSReqs which could mean an alternate app spoofing that Bundle ID could theoretically be run first and "steal" the TCC permissions from the intended app.
30 | // Even though that is not at all a risk in our environment or use case here, I prefer to do the most correct and complete thing. So, I went about learning about how to generate the proper CSReq hex strings for the TCC.db files so that the permissions we manually set can only be applied to the exact intended app.
31 |
32 | // When first investigating the TCC.db structure, the https://www.rainforestqa.com/blog/macos-tcc-db-deep-dive post pointed me to https://stackoverflow.com/questions/52706542/how-to-get-csreq-of-macos-application-on-command-line/57259004#57259004
33 | // which showed how to create the CSReq hex string using the "codesign" and "csreq" (and "xxd") commands, and I streamline the commands from that post into the following one-liner:
34 | // codesign -dr - '/path/to/some.app' 2> /dev/null | awk -F ' => ' '($1 == "designated") { print $2; exit }' | csreq -r - -b /dev/stdout | xxd -p | tr -d '[:space:]'
35 | // "xxd -c 0 -p" can be used instead of "xxd -p | tr -d '[:space:]'" on at least macOS 11.6.8 Big Sur or newer, but "xxd" on macOS 11.6 Big Sur and older do not support "-c 0" to output a single line (not sure when exactly "xxd" was updated between 11.6 and 11.6.8 since I don't have easy access to all those versions and it's odd that it was even updated within those security updates, but it was).
36 |
37 | // Upon initial testing the CSReq hex string generated this way appeared to work fine, but with more testing I noticed issues on macOS 10.14 Mojave and 10.15 Catalina where the the apps were requesting
38 | // AppleEvents TCC access when they ran even though the permissions showed up properly in the Automation list (where AppleEvents TCC permissions are listed) of the Privacy & Security pane of the System Preferences.
39 | // Although, all the AppleEvents TCC worked fine on macOS 11 Big Sur and newer with the same exact CSReqs hex string being used across all versions of macOS.
40 | // What's odd is that the same CSReqs hex strings worked fine on all versions of macOS for other TCC permissions such as Microphone, Accessibility, and Full Disk Access, all of which DO NOT specify a target/indirect object like the AppleEvents TCC permissions do.
41 | // So, I'm not exactly sure why, but the issue with the CSReq hex strings I was using appeared to only be an issue for TCC permissions which specified a target/indirect object and only on macOS 10.14 Mojave and macOS 10.15 Catalina.
42 |
43 | // With more investigation, I noticed that on all versions of macOS, the CSReq hex strings being set by macOS in the TCC.db files were slighly different than the CSReq hex strings that I generated with the command above.
44 | // To extract the CSReq hex strings from the global TCC.db for a specific Bundle ID (if that app has been granted TCC permissions), you can use the following command when running in Terminal with "Full Disk Access":
45 | // sqlite3 '/Library/Application Support/com.apple.TCC/TCC.db' 'SELECT lower(hex(csreq)) FROM access WHERE (client = "some.bundle.id")'
46 | // And you can also check the user TCC.db using the "${HOME}/Library/Application Support/com.apple.TCC/TCC.db" path.
47 |
48 | // When using the CSReq hex strings that were set by macOS in the TCC.db files, the AppleEvents TCC permissions worked properly on macOS 10.14 Mojave and macOS 10.15 Catalina, as well as macOS 11 Big Sur and newer.
49 | // So, the issue appeared to be because of the CSReq hex strings that were generated with the original command I used.
50 | // But what was wrong with those CSReq hex strings and how and why were they different from the CSReq hex strings generated by macOS?
51 |
52 | // Next, I tested to see if these two different forms of CSReq hex strings were equal to different CSReq string values with the following command to reverse the hex data back into the CSReq string form using the "csreq" command:
53 | // echo '[CSREQ-HEX-STRING]' | xxd -r -p | csreq -r - -t /dev/stdin
54 | // When reversing these two different forms of CSReq hex strings into their CSReq string values, they both equal the exact same CSReq strings.
55 | // So even though they are "equal", there must be something different with how these CSReq hex strings are generated.
56 |
57 | // While I could have just stopped there and used the CSReq hex strings extracted from the TCC.db instead of generated from the "csreq" command, I wanted to understand what was going on here and why these CSReq hex strings were different.
58 | // When trying to understand more about how these CSReq hex strings were generated, I found the source code for the "csreq" command line tool (https://opensource.apple.com/source/security_systemkeychain/security_systemkeychain-55202/src/csreq.cpp.auto.html)
59 | // and started experimenting with the native "Code Signing Services" functions of the "Security.framework" (https://developer.apple.com/documentation/security/code_signing_services?language=objc) using the ObjC-bridge in JavaScript for Automation (JXA).
60 |
61 | // That research and investigation led to me writing the following JavaScript for Automation (JXA) code which generates the CSReq hex string in different ways.
62 | // It was through this research that I figured out how to generate the CSReq hex string that is identical to how macOS generates them for the TCC.db as well as why that CSReq hex string is slightly different than the form generated by the original commands I found.
63 | // I don't know why the two different forms don't both work the same on macOS 10.14 Mojave and macOS 10.15 Catalina, but that seems to be an unintentional bug since the behavior was fixed in macOS 11 Big Sur which allowed both forms of the CSReq hex strings to work the same.
64 |
65 | // The difference turned out to be because of the CSReq data (which was then converted to hex) being created from the CSReq *string* value (using the "SecRequirementCreateWithString" function)
66 | // vs being created directly from the app on disk (using "SecStaticCodeCreateWithPath" and "SecCodeCopyDesignatedRequirement" functions).
67 | // Clearly, macOS was getting the CSReq data directly from the app on disk rather than first extracting the CSReq string from the app and then converting that CSReq string to CSReq data.
68 | // Even though the string values of the CSReqs are the same in both cases, something in the data representations are slightly different based on how that data is generated.
69 |
70 | // The following code outputs BOTH CSReq hex strings generated from the CSReq string as well as the CSReqs generated directly from the app on disk to demonstrate the difference between the two.
71 | // Interestingly, using this code shows that when getting CSReq hex strings for Apple-signed apps, the CSReq hex strings seem to always be the same regardless of whether the CSReq string or the app on disk is used to generate the CSReq data.
72 | // But, when getting the CSReq hex string for 3rd-party apps, the CSReq hex strings will be different for *some* apps depending on if the CSReq data was generated from the CSReq hex string vs the app on disk, and I'm not sure what about the specific CSReqs make the CSReq hex strings different for some 3rd-party apps and not others.
73 |
74 | // Along with generating and displaying both forms of CSReq hex strings for research purposes, it will also show the CSReq *string* value and note when the hex strings are same or different.
75 | // But, the CSReq hex generated from the app on disk is the on that should always be used when placing CSReq hex strings directly into the TCC.db for the most compatibility.
76 | // For convenience, the CSReq hex string generated from the app on disk will be copied to the clipboard when the script is run.
77 |
78 |
79 | 'use strict'
80 | // @ts-ignore: JXA-ObjC
81 | ObjC.import('Security')
82 | // @ts-ignore: JXA-ObjC
83 | ObjC.import('AppKit') // Only needed for "NSPasteboard".
84 |
85 | function run(argv) {
86 | const pathToApp = argv[0]
87 | if (!pathToApp) return 'ERROR: APP PATH ARGUMENT REQUIRED'
88 |
89 | let csreqsOutput = `APP PATH: ${pathToApp}`
90 |
91 | // "csreq" source reference: https://opensource.apple.com/source/security_systemkeychain/security_systemkeychain-55202/src/csreq.cpp.auto.html
92 |
93 | // @ts-ignore: JXA-ObjC
94 | const staticCodeRef = $()
95 | // @ts-ignore: JXA-ObjC
96 | $.SecStaticCodeCreateWithPath($.NSURL.fileURLWithPath(pathToApp), $.kSecCSDefaultFlags, staticCodeRef)
97 |
98 | // @ts-ignore: JXA-ObjC
99 | const csreqRef = $()
100 | // @ts-ignore: JXA-ObjC
101 | $.SecCodeCopyDesignatedRequirement(staticCodeRef, $.kSecCSDefaultFlags, csreqRef)
102 |
103 | // @ts-ignore: JXA-ObjC
104 | const csreqDataRef = $()
105 | // @ts-ignore: JXA-ObjC
106 | $.SecRequirementCopyData(csreqRef, $.kSecCSDefaultFlags, csreqDataRef)
107 |
108 | csreqsOutput += '\n\nCSREQ HEX FROM APP ON DISK (this is what is written to TCC.db by macOS and what *SHOULD BE USED* for AppleEvents TCC to work on Mojave & Catalina):\n'
109 | let csreqHexFromAppOnDisk = ''
110 | for (let thisByteIndex = 0, csreqBytesArray = csreqDataRef.bytes, csreqBytesLength = csreqDataRef.length; thisByteIndex < csreqBytesLength; thisByteIndex ++)
111 | csreqHexFromAppOnDisk += csreqBytesArray[thisByteIndex].toString(16).padStart(2, 0)
112 | csreqsOutput += csreqHexFromAppOnDisk
113 |
114 | // @ts-ignore: JXA-ObjC
115 | const pasteboard = $.NSPasteboard.generalPasteboard
116 | pasteboard.clearContents
117 | const didCopy = (pasteboard.writeObjects([csreqHexFromAppOnDisk])) // Copy this best from of the CSReq from the app on disk to the Clipboard for convenience.
118 | csreqsOutput += `\n>>> ${didCopy ? 'COPIED' : 'WARNING: *FAILED* TO COPY'} THIS CSREQ HEX STRING FROM THE APP ON DISK TO CLIPBOARD`
119 |
120 | // @ts-ignore: JXA-ObjC
121 | const csreqStringRef = $()
122 | // @ts-ignore: JXA-ObjC
123 | $.SecRequirementCopyString(csreqRef, $.kSecCSDefaultFlags, csreqStringRef) // This just gets the code requirement *string* from the specified app.
124 | // @ts-ignore: JXA-ObjC
125 | $.SecRequirementCreateWithString(csreqStringRef, $.kSecCSDefaultFlags, csreqRef) // Re-create the csreqRef based on the code requirements *string* instead of directly from the app on disk.
126 |
127 | csreqsOutput += `\n\nCODE REQUIREMENTS STRING:\n${csreqStringRef.js}`
128 |
129 | // THE FOLLOWING CSREQ HEX STRING *MAY* BE SLIGHTLY DIFFERENT BECAUSE SLIGHTLY DIFFERENT DATA IS PRODUCED WHEN
130 | // CODE REQUIREMENTS DATA IS PRODUCED FROM A CODE REQUIREMENTS *STRING* (using SecRequirementCreateWithString)
131 | // VS DIRECTLY FROM AN APP ON DISK (using SecStaticCodeCreateWithPath & SecCodeCopyDesignatedRequirement)
132 | // See comment above for more detailed information about these differences.
133 |
134 | // @ts-ignore: JXA-ObjC
135 | $.SecRequirementCopyData(csreqRef, $.kSecCSDefaultFlags, csreqDataRef) // This and the next steps are the same as above, but now the csreqRef is coming from the code requirements *string* instead of directly from the app on disk.
136 |
137 | csreqsOutput += '\n\nCSREQ HEX FROM CODE REQUIREMENTS STRING (this is what the "csreq" binary piped to "xxd" would output since the string is always the source which *MAY NOT WORK* on Mojave & Catalina for AppleEvents TCC which have a target app/indirect object):\n'
138 | let csreqHexFromReqString = ''
139 | for (let thisByteIndex = 0, csreqBytesArray = csreqDataRef.bytes, csreqBytesLength = csreqDataRef.length; thisByteIndex < csreqBytesLength; thisByteIndex ++)
140 | csreqHexFromReqString += csreqBytesArray[thisByteIndex].toString(16).padStart(2, 0)
141 | csreqsOutput += csreqHexFromReqString
142 |
143 | return `${csreqsOutput}
144 |
145 | THE CSREQ *HEX* FROM THE APP ON DISK VS FROM THE CODE REQUIREMENTS STRING ${((csreqHexFromAppOnDisk == csreqHexFromReqString) ? 'MATCH!' : '*DO NOT* MATCH (even though they equal the same string value)')}
146 | See comments within the code of this script for more information about these two CSReq hex strings.
147 | `
148 | }
149 |
--------------------------------------------------------------------------------
/Other Scripts/get_bluetooth_from_all_mac_specs_pages.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # shellcheck enable=add-default-case,avoid-nullary-conditions,check-unassigned-uppercase,deprecate-which,quote-safe-variables,require-double-brackets
3 |
4 | #
5 | # Created by Pico Mitchell (of Free Geek) on 8/9/22.
6 | #
7 | # MIT License
8 | #
9 | # Copyright (c) 2022 Free Geek
10 | #
11 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"),
12 | # to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
13 | # and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
14 | #
15 | # The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
16 | #
17 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
19 | # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20 | #
21 |
22 | PATH='/usr/bin:/bin:/usr/sbin:/sbin'
23 |
24 | declare -a all_mac_identification_pages=() # All Mac identification pages are listed on https://support.apple.com/HT213325 as well as in the "On the product or its packaging" section of https://support.apple.com/HT201581
25 | all_mac_identification_pages+=( 'HT201300' ) # MacBook Pro
26 | all_mac_identification_pages+=( 'HT201862' ) # MacBook Air
27 | all_mac_identification_pages+=( 'HT201608' ) # MacBook
28 | all_mac_identification_pages+=( 'HT201634' ) # iMac
29 | all_mac_identification_pages+=( 'HT201894' ) # Mac mini
30 | all_mac_identification_pages+=( 'HT213073' ) # Mac Studio
31 | all_mac_identification_pages+=( 'HT202888' ) # Mac Pro
32 |
33 | every_bluetooth_version=''
34 |
35 | every_bluetooth_5dot3_model=''
36 | every_bluetooth_5_model=''
37 | every_bluetooth_4dot2_model=''
38 | every_bluetooth_4_model='Macmini5,3+' # Mid 2011 Mac mini Server (Macmini5,3) is not listed in the Specs pages.
39 | every_bluetooth_2dot1plusEDR_model=''
40 | every_bluetooth_other_version_model=''
41 | every_error_model=''
42 |
43 | for this_mac_idenification_page in "${all_mac_identification_pages[@]}"; do
44 | this_mac_idenification_page_source="$(curl -m 5 -sfL "https://support.apple.com/${this_mac_idenification_page}")"
45 |
46 | this_model_identifier=''
47 | while IFS='' read -r this_model_id_or_specs_url; do
48 | if [[ "${this_model_id_or_specs_url}" == 'https://'* ]]; then
49 | echo " (${this_model_id_or_specs_url}):"
50 | specs_page_source="$(curl -m 5 -sfL "${this_model_id_or_specs_url}")"
51 | bluetooth_element_from_page="$(echo "${specs_page_source}" | xmllint --html --xpath '//li[contains(text(),"Bluetooth")]/text()' - 2> /dev/null)"
52 |
53 | if [[ -z "${bluetooth_element_from_page}" ]]; then
54 | bluetooth_element_from_page="$(echo "${specs_page_source}" | xmllint --html --xpath '//p[contains(text(),"Bluetooth")]/text()' - 2> /dev/null)"
55 | fi
56 |
57 | if [[ -z "${bluetooth_element_from_page}" ]]; then
58 | echo 'ERROR DETECTING BLUETOOTH FOR MODEL'
59 | every_error_model+="${this_model_identifier}+"
60 | else
61 | bluetooth_version="$(echo "${bluetooth_element_from_page}" | tr -dc '[:digit:].')"
62 |
63 | if [[ "${bluetooth_version}" == '.'* ]]; then
64 | bluetooth_version="${bluetooth_version#.}"
65 | fi
66 |
67 | if [[ "${bluetooth_element_from_page}" == *'EDR'* ]]; then
68 | bluetooth_version+=' + EDR'
69 | fi
70 |
71 | case "${bluetooth_version}" in
72 | '5.3')
73 | every_bluetooth_5dot3_model+="${this_model_identifier}+"
74 | ;;
75 | '5.0')
76 | every_bluetooth_5_model+="${this_model_identifier}+"
77 | ;;
78 | '4.2')
79 | every_bluetooth_4dot2_model+="${this_model_identifier}+"
80 | ;;
81 | '4.0')
82 | every_bluetooth_4_model+="${this_model_identifier}+"
83 | ;;
84 | '2.1 + EDR')
85 | every_bluetooth_2dot1plusEDR_model+="${this_model_identifier}+"
86 | ;;
87 | *)
88 | every_bluetooth_other_version_model+="${this_model_identifier}+"
89 | ;;
90 | esac
91 |
92 | every_bluetooth_version+=$'\n'"${bluetooth_version}"
93 |
94 | echo "Bluetooth ${bluetooth_version}"
95 | fi
96 | else
97 | this_model_identifier="${this_model_id_or_specs_url}"
98 | echo -en "\n${this_model_identifier}"
99 | fi
100 | done < <(echo "${this_mac_idenification_page_source}" | awk -F ':|"' '/Model Identifier:/ { gsub(" ", " ", $NF); gsub(", ", "+", $NF); gsub("; ", "+", $NF); gsub(" ", "", $NF); gsub("
", "", $NF); print ""; print $NF } /Tech Specs:/ { print "https:" $4 }')
101 | # echo "${this_mac_idenification_page_source}" | xmllint --html --xpath '//a[contains(@href,"/kb/SP")]/@href' - 2> /dev/null | tr '"' '\n' | grep '/kb/SP' | sort -ur
102 | done
103 |
104 | echo -e '\n\nEvery Bluetooth Verison'
105 | echo "${every_bluetooth_version}" | sort -urV
106 |
107 | echo 'Bluetooth 5.3'
108 | every_bluetooth_5dot3_model="$(echo "${every_bluetooth_5dot3_model%+}" | tr '+' '\n' | sort -uV)"
109 | echo "\"${every_bluetooth_5dot3_model//$'\n'/", "}\""
110 |
111 | echo -e '\nBluetooth 5.0'
112 | every_bluetooth_5_model="$(echo "${every_bluetooth_5_model%+}" | tr '+' '\n' | sort -uV)"
113 | echo "\"${every_bluetooth_5_model//$'\n'/", "}\""
114 |
115 | echo -e '\nBluetooth 4.2'
116 | every_bluetooth_4dot2_model="$(echo "${every_bluetooth_4dot2_model%+}" | tr '+' '\n' | sort -uV)"
117 | echo "\"${every_bluetooth_4dot2_model//$'\n'/", "}\""
118 |
119 | echo -e '\nBluetooth 4.0'
120 | every_bluetooth_4_model="$(echo "${every_bluetooth_4_model%+}" | tr '+' '\n' | sort -uV)"
121 | echo "\"${every_bluetooth_4_model//$'\n'/", "}\""
122 |
123 | echo -e '\nBluetooth 2.1 + EDR'
124 | every_bluetooth_2dot1plusEDR_model="$(echo "${every_bluetooth_2dot1plusEDR_model%+}" | tr '+' '\n' | sort -uV)"
125 | echo "\"${every_bluetooth_2dot1plusEDR_model//$'\n'/", "}\""
126 |
127 | echo -e '\nBluetooth OTHER VERSION'
128 | every_bluetooth_other_version_model="$(echo "${every_bluetooth_other_version_model%+}" | tr '+' '\n' | sort -uV)"
129 | echo "\"${every_bluetooth_other_version_model//$'\n'/", "}\""
130 |
131 | echo -e '\nERROR DETECTING BLUETOOTH'
132 | every_error_model="$(echo "${every_error_model%+}" | tr '+' '\n' | sort -uV)"
133 | echo "\"${every_error_model//$'\n'/", "}\""
134 |
135 | echo ''
136 |
137 | # Example output from 2/1/23:
138 |
139 | # Every Bluetooth Verison
140 | # 5.3
141 | # 5.0
142 | # 4.2
143 | # 4.0
144 | # 2.1 + EDR
145 |
146 | # Bluetooth 5.3
147 | # "Mac14,3", "Mac14,5", "Mac14,6", "Mac14,9", "Mac14,10", "Mac14,12"
148 |
149 | # Bluetooth 5.0
150 | # "Mac13,1", "Mac13,2", "Mac14,2", "Mac14,7", "MacBookAir9,1", "MacBookAir10,1", "MacBookPro15,1", "MacBookPro15,2", "MacBookPro15,3", "MacBookPro15,4", "MacBookPro16,1", "MacBookPro16,2", "MacBookPro16,3", "MacBookPro16,4", "MacBookPro17,1", "MacBookPro18,1", "MacBookPro18,2", "MacBookPro18,3", "MacBookPro18,4", "MacPro7,1", "Macmini8,1", "Macmini9,1", "iMac20,1", "iMac20,2", "iMac21,1", "iMac21,2", "iMacPro1,1"
151 |
152 | # Bluetooth 4.2
153 | # "MacBook10,1", "MacBookAir8,1", "MacBookAir8,2", "MacBookPro11,4", "MacBookPro11,5", "MacBookPro13,1", "MacBookPro13,2", "MacBookPro13,3", "MacBookPro14,1", "MacBookPro14,2", "MacBookPro14,3", "iMac18,1", "iMac18,2", "iMac18,3", "iMac19,1", "iMac19,2"
154 |
155 | # Bluetooth 4.0
156 | # "MacBook8,1", "MacBook9,1", "MacBookAir4,1", "MacBookAir4,2", "MacBookAir5,1", "MacBookAir5,2", "MacBookAir6,1", "MacBookAir6,2", "MacBookAir7,1", "MacBookAir7,2", "MacBookPro9,1", "MacBookPro9,2", "MacBookPro10,1", "MacBookPro10,2", "MacBookPro11,1", "MacBookPro11,2", "MacBookPro11,3", "MacBookPro12,1", "MacPro6,1", "Macmini5,1", "Macmini5,2", "Macmini5,3", "Macmini6,1", "Macmini6,2", "Macmini7,1", "iMac13,1", "iMac13,2", "iMac14,1", "iMac14,2", "iMac14,4", "iMac15,1", "iMac16,1", "iMac16,2", "iMac17,1"
157 |
158 | # Bluetooth 2.1 + EDR
159 | # "MacBook5,2", "MacBook6,1", "MacBook7,1", "MacBookAir2,1", "MacBookAir3,1", "MacBookAir3,2", "MacBookPro4,1", "MacBookPro5,1", "MacBookPro5,2", "MacBookPro5,3", "MacBookPro5,5", "MacBookPro6,1", "MacBookPro6,2", "MacBookPro7,1", "MacBookPro8,1", "MacBookPro8,2", "MacBookPro8,3", "MacPro4,1", "MacPro5,1", "Macmini3,1", "Macmini4,1", "iMac9,1", "iMac10,1", "iMac11,2", "iMac11,3", "iMac12,1", "iMac12,2"
160 |
161 | # Bluetooth OTHER VERSION
162 | # ""
163 |
164 | # ERROR DETECTING BLUETOOTH
165 | # ""
166 |
--------------------------------------------------------------------------------
/Other Scripts/get_power_adapters_from_all_mac_specs_pages.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # shellcheck enable=add-default-case,avoid-nullary-conditions,check-unassigned-uppercase,deprecate-which,quote-safe-variables,require-double-brackets
3 |
4 | #
5 | # Created by Pico Mitchell (of Free Geek) on 8/9/22.
6 | #
7 | # MIT License
8 | #
9 | # Copyright (c) 2022 Free Geek
10 | #
11 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"),
12 | # to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
13 | # and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
14 | #
15 | # The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
16 | #
17 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
19 | # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20 | #
21 |
22 | PATH='/usr/bin:/bin:/usr/sbin:/sbin'
23 |
24 | # The https://support.apple.com/en-us/HT201700 page is also useful to identify power adapters, but it's not as specific and doesn't include Model IDs, so there's not much value in scraping it.
25 |
26 | declare -a all_mac_identification_pages=() # All Mac identification pages are listed on https://support.apple.com/HT213325 as well as in the "On the product or its packaging" section of https://support.apple.com/HT201581
27 | all_mac_identification_pages+=( 'HT201300' ) # MacBook Pro
28 | all_mac_identification_pages+=( 'HT201862' ) # MacBook Air
29 | all_mac_identification_pages+=( 'HT201608' ) # MacBook
30 | # all_mac_identification_pages+=( 'HT201634' ) # iMac
31 | # all_mac_identification_pages+=( 'HT201894' ) # Mac mini
32 | # all_mac_identification_pages+=( 'HT213073' ) # Mac Studio
33 | # all_mac_identification_pages+=( 'HT202888' ) # Mac Pro
34 |
35 | # NOTE: MagSafe 1 lists have older Model IDs that aren't included on the specs pages pre-included in them.
36 | every_85w_magsafe1_model='MacBookPro1,1+MacBookPro1,2+MacBookPro2,1+MacBookPro2,2+MacBookPro3,1+'
37 | every_60w_magsafe1_model='MacBook1,1+MacBook2,1+MacBook3,1+MacBook4,1+MacBook5,1+'
38 | every_45w_magsafe1_model='MacBookAir1,1+'
39 | every_85w_magsafe2_model=''
40 | every_60w_magsafe2_model=''
41 | every_45w_magsafe2_model=''
42 | every_96w_usbc_model=''
43 | every_87w_usbc_model=''
44 | every_67w_usbc_model=''
45 | every_61w_usbc_model=''
46 | every_30w_usbc_model=''
47 | every_29w_usbc_model=''
48 |
49 | # NOTE: The newer Apple Silicon Macs with MagSafe 3 tend to support multiple different wattages with and without fast
50 | # charge capabilities so their descriptions are not as clear and clean to analyze in code as the past power adapter types
51 | # since the MagSafe 3 capability is listed on a separate line as just the cable and not the power adapter itself.
52 | # So, instead of trying, just manually check the outputs and come up with more concise descriptions and pre-add them to their respective lists instead.
53 | every_140w_magsafe3_model='MacBookPro18,1+MacBookPro18,2+Mac14,6+Mac14,10+'
54 | every_67w_or_96w_magsafe3_model='MacBookPro18,3+MacBookPro18,4+Mac14,5+Mac14,9+'
55 | every_30w_or_35W_dp_or_67w_magsafe3_model='Mac14,2+'
56 |
57 | every_unknown_model=''
58 |
59 | for this_mac_idenification_page in "${all_mac_identification_pages[@]}"; do
60 | this_mac_idenification_page_source="$(curl -m 5 -sfL "https://support.apple.com/${this_mac_idenification_page}")"
61 |
62 | this_model_identifier=''
63 | while IFS='' read -r this_model_id_or_specs_url; do
64 | if [[ "${this_model_id_or_specs_url}" == 'https://'* ]]; then
65 | echo " (${this_model_id_or_specs_url}):"
66 | power_adapter_elements_from_page="$(curl -m 5 -sfL "${this_model_id_or_specs_url}" | xmllint --html --xpath '//*[contains(text(),"Power Adapter") or contains(text(),"MagSafe")]' - 2> /dev/null)"
67 | power_adapter_elements_from_page="${power_adapter_elements_from_page//>>$'\n'<}"
68 | power_adapter_elements_from_page="${power_adapter_elements_from_page//; /$'\n'}"
69 |
70 | # Suppress ShellCheck warning to use bash string replacement since it cannot do this regex style replacement.
71 | # shellcheck disable=SC2001
72 | power_adapter_elements_from_page="$(echo "${power_adapter_elements_from_page}" | sed 's/<[^>]*>//g')"
73 |
74 | if [[ "${power_adapter_elements_from_page}" == *'with cable management'* ]]; then
75 | power_adapter_elements_from_page="$(echo "${power_adapter_elements_from_page}" | grep 'with cable management')" # Remove extraneous lines for MagSafe 2 and MagSafe 1 that aren't for the actual power adapter.
76 | power_adapter_elements_from_page="${power_adapter_elements_from_page% with cable management*}"
77 | else
78 | power_adapter_elements_from_page="$(echo "${power_adapter_elements_from_page}" | grep -v 'USB-C power port\|Power Adapter Extension Cable')" # Remove extraneous lines for some USB-C adapters.
79 | power_adapter_elements_from_page="$(echo "${power_adapter_elements_from_page}" | grep -v 'MagSafe 3 port\|MagSafe 3 charging port\|USB-C to MagSafe 3 Cable (2 m)')" # Remove extraneous lines for some MagSafe 3 adapters.
80 | fi
81 |
82 | power_adapter_elements_from_page="$(echo "${power_adapter_elements_from_page//‑/-}" | sort -u)"
83 | echo -e "\t${power_adapter_elements_from_page//$'\n'/$'\n\t'}"
84 |
85 | power_adapter_elements_from_page_lowercase="$(echo "${power_adapter_elements_from_page}" | tr '[:upper:]' '[:lower:]')"
86 | case "${power_adapter_elements_from_page_lowercase}" in
87 | '85w magsafe power adapter')
88 | every_85w_magsafe1_model+="${this_model_identifier}+"
89 | ;;
90 | '60w magsafe power adapter')
91 | every_60w_magsafe1_model+="${this_model_identifier}+"
92 | ;;
93 | '60w or 85w magsafe power adapter')
94 | if [[ "${this_model_identifier}" == 'MacBookPro5,3' || "${this_model_identifier}" == 'MacBookPro5,4' ]]; then
95 | # The specs pages (https://support.apple.com/kb/SP544?locale=en_US) combine MacBookPro5,3 and MacBookPro5,4 (which isn't even listed, but check for it just in case),
96 | # and list them both as MacBookPro5,3 even though "MacBook Pro (15-inch, 2.53 GHz, Mid 2009)" is actually MacBookPro5,4 (and not MacBookPro5,3)
97 | # and state "60W or 85W MagSafe Power Adapter" in the "Battery and power" section, but the table in the "15-inch Configurations" section properly shows
98 | # that the "2.53GHz MacBook Pro (MC118LL/A)" model (which is actually MacBookPro5,4), takes the 60W MagSafe Power Adapter while the other configurations
99 | # (which are the MacBookPro5,3 models) take the 85W MagSafe Power Adapter rather than both of these models being able to take either/or power adapter wattage.
100 | # 60W = MacBookPro5,4 / MC118LL/A: https://everymac.com/systems/apple/macbook_pro/specs/macbook-pro-core-2-duo-2.53-aluminum-15-mid-2009-sd-unibody-specs.html
101 | # 85W = MacBookPro5,3 / MB985LL/A: https://everymac.com/systems/apple/macbook_pro/specs/macbook-pro-core-2-duo-2.66-aluminum-15-mid-2009-sd-unibody-specs.html
102 | # 85W = MacBookPro5,3 / MB986LL/A: https://everymac.com/systems/apple/macbook_pro/specs/macbook-pro-core-2-duo-2.8-aluminum-15-mid-2009-sd-unibody-specs.html
103 |
104 | echo -e '\tMANUAL CORRECTION: MacBookPro5,4 = 60W MagSafe 1\n\tMANUAL CORRECTION: MacBookPro5,3 = 85W MagSafe 1'
105 | every_85w_magsafe1_model+="MacBookPro5,3+"
106 | every_60w_magsafe1_model+="MacBookPro5,4+"
107 | else
108 | echo -e '\tERROR: UNKNOWN Power Adater'
109 | every_unknown_model+="${this_model_identifier}+"
110 | fi
111 | ;;
112 | '45w magsafe power adapter')
113 | every_45w_magsafe1_model+="${this_model_identifier}+"
114 | ;;
115 | '85w magsafe 2 power adapter')
116 | every_85w_magsafe2_model+="${this_model_identifier}+"
117 | ;;
118 | '60w magsafe 2 power adapter')
119 | every_60w_magsafe2_model+="${this_model_identifier}+"
120 | ;;
121 | '45w magsafe 2 power adapter')
122 | every_45w_magsafe2_model+="${this_model_identifier}+"
123 | ;;
124 | '96w usb-c power adapter')
125 | every_96w_usbc_model+="${this_model_identifier}+"
126 | ;;
127 | '87w usb-c power adapter')
128 | every_87w_usbc_model+="${this_model_identifier}+"
129 | ;;
130 | '67w usb-c power adapter')
131 | every_67w_usbc_model+="${this_model_identifier}+"
132 | ;;
133 | '61w usb-c power adapter')
134 | every_61w_usbc_model+="${this_model_identifier}+"
135 | ;;
136 | '30w usb-c power adapter')
137 | every_30w_usbc_model+="${this_model_identifier}+"
138 | ;;
139 | '29w usb-c power adapter')
140 | every_29w_usbc_model+="${this_model_identifier}+"
141 | ;;
142 | *)
143 | if [[ "+${every_140w_magsafe3_model}" == *"+${this_model_identifier}+"* ]]; then
144 | echo -e '\tMANUAL CONCISE DESCRIPTION: 140W USB-C/MagSafe 3'
145 | elif [[ "+${every_67w_or_96w_magsafe3_model}" == *"+${this_model_identifier}+"* ]]; then
146 | echo -e '\tMANUAL CONCISE DESCRIPTION: 67W or 96W USB-C/MagSafe 3'
147 | elif [[ "+${every_30w_or_35W_dp_or_67w_magsafe3_model}" == *"+${this_model_identifier}+"* ]]; then
148 | echo -e '\tMANUAL CONCISE DESCRIPTION: 30W or 35W Dual Port or 67W USB-C/MagSafe 3'
149 | else
150 | echo -e '\tERROR: UNKNOWN Power Adater'
151 | every_unknown_model+="${this_model_identifier}+"
152 | fi
153 | ;;
154 | esac
155 | else
156 | this_model_identifier="${this_model_id_or_specs_url}"
157 | echo -en "\n${this_model_identifier}"
158 | fi
159 | done < <(echo "${this_mac_idenification_page_source}" | awk -F ':|"' '/Model Identifier:/ { gsub(" ", " ", $NF); gsub(", ", "+", $NF); gsub("; ", "+", $NF); gsub(" ", "", $NF); gsub("
", "", $NF); print ""; print $NF } /Tech Specs:/ { print "https:" $4 }')
160 | # echo "${this_mac_idenification_page_source}" | xmllint --html --xpath '//a[contains(@href,"/kb/SP")]/@href' - 2> /dev/null | tr '"' '\n' | grep '/kb/SP' | sort -ur
161 | done
162 |
163 | echo -e '\n\n85W MagSafe 1'
164 | every_85w_magsafe1_model="$(echo "${every_85w_magsafe1_model%+}" | tr '+' '\n' | sort -uV)"
165 | echo "\"${every_85w_magsafe1_model//$'\n'/", "}\""
166 |
167 | echo -e '\n60W MagSafe 1'
168 | every_60w_magsafe1_model="$(echo "${every_60w_magsafe1_model%+}" | tr '+' '\n' | sort -uV)"
169 | echo "\"${every_60w_magsafe1_model//$'\n'/", "}\""
170 |
171 | echo -e '\n45W MagSafe 1'
172 | every_45w_magsafe1_model="$(echo "${every_45w_magsafe1_model%+}" | tr '+' '\n' | sort -uV)"
173 | echo "\"${every_45w_magsafe1_model//$'\n'/", "}\""
174 |
175 | echo -e '\n85W MagSafe 2'
176 | every_85w_magsafe2_model="$(echo "${every_85w_magsafe2_model%+}" | tr '+' '\n' | sort -uV)"
177 | echo "\"${every_85w_magsafe2_model//$'\n'/", "}\""
178 |
179 | echo -e '\n60W MagSafe 2'
180 | every_60w_magsafe2_model="$(echo "${every_60w_magsafe2_model%+}" | tr '+' '\n' | sort -uV)"
181 | echo "\"${every_60w_magsafe2_model//$'\n'/", "}\""
182 |
183 | echo -e '\n45W MagSafe 2'
184 | every_45w_magsafe2_model="$(echo "${every_45w_magsafe2_model%+}" | tr '+' '\n' | sort -uV)"
185 | echo "\"${every_45w_magsafe2_model//$'\n'/", "}\""
186 |
187 | echo -e '\n96W USB-C'
188 | every_96w_usbc_model="$(echo "${every_96w_usbc_model%+}" | tr '+' '\n' | sort -uV)"
189 | echo "\"${every_96w_usbc_model//$'\n'/", "}\""
190 |
191 | echo -e '\n87W USB-C'
192 | every_87w_usbc_model="$(echo "${every_87w_usbc_model%+}" | tr '+' '\n' | sort -uV)"
193 | echo "\"${every_87w_usbc_model//$'\n'/", "}\""
194 |
195 | echo -e '\n67W USB-C'
196 | every_67w_usbc_model="$(echo "${every_67w_usbc_model%+}" | tr '+' '\n' | sort -uV)"
197 | echo "\"${every_67w_usbc_model//$'\n'/", "}\""
198 |
199 | echo -e '\n61W USB-C'
200 | every_61w_usbc_model="$(echo "${every_61w_usbc_model%+}" | tr '+' '\n' | sort -uV)"
201 | echo "\"${every_61w_usbc_model//$'\n'/", "}\""
202 |
203 | echo -e '\n30W USB-C'
204 | every_30w_usbc_model="$(echo "${every_30w_usbc_model%+}" | tr '+' '\n' | sort -uV)"
205 | echo "\"${every_30w_usbc_model//$'\n'/", "}\""
206 |
207 | echo -e '\n29W USB-C'
208 | every_29w_usbc_model="$(echo "${every_29w_usbc_model%+}" | tr '+' '\n' | sort -uV)"
209 | echo "\"${every_29w_usbc_model//$'\n'/", "}\""
210 |
211 | echo -e '\n140W USB-C/MagSafe 3'
212 | every_140w_magsafe3_model="$(echo "${every_140w_magsafe3_model%+}" | tr '+' '\n' | sort -uV)"
213 | echo "\"${every_140w_magsafe3_model//$'\n'/", "}\""
214 |
215 | echo -e '\n67W or 96W USB-C/MagSafe 3'
216 | every_67w_or_96w_magsafe3_model="$(echo "${every_67w_or_96w_magsafe3_model%+}" | tr '+' '\n' | sort -uV)"
217 | echo "\"${every_67w_or_96w_magsafe3_model//$'\n'/", "}\""
218 |
219 | echo -e '\n30W or 35W Dual Port or 67W USB-C/MagSafe 3'
220 | every_30w_or_35W_dp_or_67w_magsafe3_model="$(echo "${every_30w_or_35W_dp_or_67w_magsafe3_model%+}" | tr '+' '\n' | sort -uV)"
221 | echo "\"${every_30w_or_35W_dp_or_67w_magsafe3_model//$'\n'/", "}\""
222 |
223 | echo -e '\nUNKNOWN Power Adapter (REQUIRES MANUAL EXAMINATION)'
224 | every_unknown_model="$(echo "${every_unknown_model%+}" | tr '+' '\n' | sort -uV)"
225 | echo "\"${every_unknown_model//$'\n'/", "}\""
226 |
227 | echo ''
228 |
229 | # Example output from 2/1/23:
230 |
231 | # 85W MagSafe 1
232 | # "MacBookPro1,1", "MacBookPro1,2", "MacBookPro2,1", "MacBookPro2,2", "MacBookPro3,1", "MacBookPro4,1", "MacBookPro5,1", "MacBookPro5,2", "MacBookPro5,3", "MacBookPro6,1", "MacBookPro6,2", "MacBookPro8,2", "MacBookPro8,3", "MacBookPro9,1"
233 |
234 | # 60W MagSafe 1
235 | # "MacBook1,1", "MacBook2,1", "MacBook3,1", "MacBook4,1", "MacBook5,1", "MacBook5,2", "MacBook6,1", "MacBook7,1", "MacBookPro5,4", "MacBookPro5,5", "MacBookPro7,1", "MacBookPro8,1", "MacBookPro9,2"
236 |
237 | # 45W MagSafe 1
238 | # "MacBookAir1,1", "MacBookAir2,1", "MacBookAir3,1", "MacBookAir3,2", "MacBookAir4,1", "MacBookAir4,2"
239 |
240 | # 85W MagSafe 2
241 | # "MacBookPro10,1", "MacBookPro11,2", "MacBookPro11,3", "MacBookPro11,4", "MacBookPro11,5"
242 |
243 | # 60W MagSafe 2
244 | # "MacBookPro10,2", "MacBookPro11,1", "MacBookPro12,1"
245 |
246 | # 45W MagSafe 2
247 | # "MacBookAir5,1", "MacBookAir5,2", "MacBookAir6,1", "MacBookAir6,2", "MacBookAir7,1", "MacBookAir7,2"
248 |
249 | # 96W USB-C
250 | # "MacBookPro16,1", "MacBookPro16,4"
251 |
252 | # 87W USB-C
253 | # "MacBookPro13,3", "MacBookPro14,3", "MacBookPro15,1", "MacBookPro15,3"
254 |
255 | # 67W USB-C
256 | # "Mac14,7"
257 |
258 | # 61W USB-C
259 | # "MacBookPro13,1", "MacBookPro13,2", "MacBookPro14,1", "MacBookPro14,2", "MacBookPro15,2", "MacBookPro15,4", "MacBookPro16,2", "MacBookPro16,3", "MacBookPro17,1"
260 |
261 | # 30W USB-C
262 | # "MacBook10,1", "MacBookAir8,1", "MacBookAir8,2", "MacBookAir9,1", "MacBookAir10,1"
263 |
264 | # 29W USB-C
265 | # "MacBook8,1", "MacBook9,1"
266 |
267 | # 140W USB-C/MagSafe 3
268 | # "Mac14,6", "Mac14,10", "MacBookPro18,1", "MacBookPro18,2"
269 |
270 | # 67W or 96W USB-C/MagSafe 3
271 | # "Mac14,5", "Mac14,9", "MacBookPro18,3", "MacBookPro18,4"
272 |
273 | # 30W or 35W Dual Port or 67W USB-C/MagSafe 3
274 | # "Mac14,2"
275 |
276 | # UNKNOWN Power Adapter (REQUIRES MANUAL EXAMINATION)
277 | # ""
--------------------------------------------------------------------------------
/Other Scripts/get_specs_url_from_serial.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # shellcheck enable=add-default-case,avoid-nullary-conditions,check-unassigned-uppercase,deprecate-which,quote-safe-variables,require-double-brackets
3 |
4 | #
5 | # Created by Pico Mitchell (of Free Geek) on 8/9/22.
6 | #
7 | # MIT License
8 | #
9 | # Copyright (c) 2022 Free Geek
10 | #
11 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"),
12 | # to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
13 | # and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
14 | #
15 | # The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
16 | #
17 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
19 | # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20 | #
21 |
22 | PATH='/usr/bin:/bin:/usr/sbin:/sbin'
23 |
24 | serial_number="$1" # Use a specified serial number from the first argument.
25 |
26 | if [[ -z "$1" ]]; then # Get the current Mac serial number if no argument specified.
27 | serial_number="$(/usr/libexec/PlistBuddy -c 'Print :0:IOPlatformSerialNumber' /dev/stdin <<< "$(ioreg -arc IOPlatformExpertDevice -k IOPlatformSerialNumber -d 1)" 2> /dev/null)"
28 | fi
29 |
30 | # The following URLs were discovered from examining how "https://support.apple.com/specs/${serial_number}" loads the specs URL via JavaScript (as of August 9th, 2022 in case this breaks in the future).
31 |
32 | serial_search_results_json="$(curl -m 10 -sfL "https://km.support.apple.com/kb/index?page=categorydata&serialnumber=${serial_number}" 2> /dev/null)" # I have seem this URL API timeout after 5 seconds when called multiple times rapidly (likely because of rate limiting), so give it a 10 second timeout which seems to always work.
33 |
34 | if [[ -z "${serial_search_results_json}" ]]; then
35 | >&2 echo 'INTERNET REQUIRED - SERIAL SEARCH FAILED'
36 | exit 1
37 | fi
38 |
39 | IFS=$'\n' read -rd '' -a serial_search_results_values < <(osascript -l 'JavaScript' -e '
40 | function run(argv) {
41 | const serialSearchResultsDict = JSON.parse(argv[0])
42 | return [serialSearchResultsDict.name, serialSearchResultsDict.id, serialSearchResultsDict.parent, serialSearchResultsDict.grandparent, serialSearchResultsDict.greatgrandparent].join("\n")
43 | }
44 | ' -- "${serial_search_results_json}" 2> /dev/null)
45 | # NOTE: Because of JavaScript behavior, any "undefined" (or "null") values in an array would be turned into empty strings when using "join", making them empty lines.
46 | # And, because of bash behaviors with whitespace IFS treating consecutive whitespace as a single delimiter (explained in https://mywiki.wooledge.org/IFS),
47 | # any empty lines will NOT be included in the bash array being created with this technique to set all lines to an array.
48 | # So, that means if any of these values are not found, the bash array WILL NOT have a count of exactly 5 which we can check to verify all required values were properly loaded.
49 |
50 | if (( ${#serial_search_results_values[@]} != 5 )); then
51 | >&2 echo "UNEXPECTED ERROR - SERIAL SEARCH DOES NOT CONTAIN EXPECTED/REQUIRED VALUES: $(declare -p serial_search_results_values | cut -d '=' -f 2-)"
52 | exit 2
53 | fi
54 |
55 | specs_search_results_json="$(curl -m 10 -sfL "https://km.support.apple.com/kb/index?page=specs_browse&category=${serial_search_results_values[1]}&parent=${serial_search_results_values[2]}&grandparent=${serial_search_results_values[3]}&greatgrandparent=${serial_search_results_values[4]}" 2> /dev/null)"
56 |
57 | if [[ -z "${specs_search_results_json}" ]]; then
58 | >&2 echo 'INTERNET REQUIRED - SPECS SEARCH FAILED'
59 | exit 3
60 | fi
61 |
62 | specs_url_kb_part="$(osascript -l 'JavaScript' -e 'run = argv => JSON.parse(argv[0]).specs[0].url' -- "${specs_search_results_json}" 2> /dev/null)"
63 |
64 | if [[ -z "${specs_url_kb_part}" ]]; then
65 | >&2 echo 'UNEXPECTED ERROR - SPECS SEARCH DOES NOT CONTAIN KB SPECS URL'
66 | exit 4
67 | fi
68 |
69 | specs_url="https://support.apple.com${specs_url_kb_part}"
70 |
71 | echo "Specs URL for \"${serial_search_results_values[0]}\": ${specs_url}"
72 |
--------------------------------------------------------------------------------
/Other Scripts/get_truetone_from_all_mac_specs_pages.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # shellcheck enable=add-default-case,avoid-nullary-conditions,check-unassigned-uppercase,deprecate-which,quote-safe-variables,require-double-brackets
3 |
4 | #
5 | # Created by Pico Mitchell (of Free Geek) on 8/9/22.
6 | #
7 | # MIT License
8 | #
9 | # Copyright (c) 2022 Free Geek
10 | #
11 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"),
12 | # to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
13 | # and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
14 | #
15 | # The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
16 | #
17 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
19 | # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20 | #
21 |
22 | PATH='/usr/bin:/bin:/usr/sbin:/sbin'
23 |
24 | declare -a all_mac_identification_pages=() # All Mac identification pages are listed on https://support.apple.com/HT213325 as well as in the "On the product or its packaging" section of https://support.apple.com/HT201581
25 | all_mac_identification_pages+=( 'HT201300' ) # MacBook Pro
26 | all_mac_identification_pages+=( 'HT201862' ) # MacBook Air
27 | all_mac_identification_pages+=( 'HT201608' ) # MacBook
28 | all_mac_identification_pages+=( 'HT201634' ) # iMac
29 | #all_mac_identification_pages+=( 'HT201894' ) # Mac mini
30 | #all_mac_identification_pages+=( 'HT213073' ) # Mac Studio
31 | #all_mac_identification_pages+=( 'HT202888' ) # Mac Pro
32 |
33 | every_truetone_model=''
34 |
35 | for this_mac_idenification_page in "${all_mac_identification_pages[@]}"; do
36 | this_mac_idenification_page_source="$(curl -m 5 -sfL "https://support.apple.com/${this_mac_idenification_page}")"
37 |
38 | this_model_identifier=''
39 | while IFS='' read -r this_model_id_or_specs_url; do
40 | if [[ "${this_model_id_or_specs_url}" == 'https://'* ]]; then
41 | echo " (${this_model_id_or_specs_url}):"
42 | truetone_element_from_page="$(curl -m 5 -sfL "${this_model_id_or_specs_url}" | xmllint --html --xpath 'string(//*[contains(text(),"True Tone")])' - 2> /dev/null)"
43 | if [[ -n "${truetone_element_from_page}" ]]; then
44 | echo "Supports True Tone"
45 | every_truetone_model+="${this_model_identifier}+"
46 | else
47 | echo "DOES NOT SUPPORT True Tone"
48 | fi
49 | else
50 | this_model_identifier="${this_model_id_or_specs_url}"
51 | echo -en "\n${this_model_identifier}"
52 | fi
53 | done < <(echo "${this_mac_idenification_page_source}" | awk -F ':|"' '/Model Identifier:/ { gsub(" ", " ", $NF); gsub(", ", "+", $NF); gsub("; ", "+", $NF); gsub(" ", "", $NF); gsub("
", "", $NF); print ""; print $NF } /Tech Specs:/ { print "https:" $4 }')
54 | # echo "${this_mac_idenification_page_source}" | xmllint --html --xpath '//a[contains(@href,"/kb/SP")]/@href' - 2> /dev/null | tr '"' '\n' | grep '/kb/SP' | sort -ur
55 | done
56 |
57 | echo -e "\n\nSupports True Tone"
58 | every_truetone_model="$(echo "${every_truetone_model%+}" | tr '+' '\n' | sort -uV)"
59 | echo "\"${every_truetone_model//$'\n'/", "}\""
60 |
61 | echo ''
62 |
63 | # Example output from 2/1/23:
64 |
65 | # Supports True Tone
66 | # "Mac14,2", "Mac14,5", "Mac14,6", "Mac14,7", "Mac14,9", "Mac14,10", "MacBookAir8,2", "MacBookAir9,1", "MacBookAir10,1", "MacBookPro15,1", "MacBookPro15,2", "MacBookPro15,3", "MacBookPro15,4", "MacBookPro16,1", "MacBookPro16,2", "MacBookPro16,3", "MacBookPro16,4", "MacBookPro17,1", "MacBookPro18,1", "MacBookPro18,2", "MacBookPro18,3", "MacBookPro18,4", "iMac20,1", "iMac20,2", "iMac21,1", "iMac21,2"
67 |
--------------------------------------------------------------------------------
/Other Scripts/quit_apps_by_bundle_id.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | # shellcheck enable=add-default-case,avoid-nullary-conditions,check-unassigned-uppercase,deprecate-which,quote-safe-variables
3 |
4 | quit_apps_by_bundle_id() { # Arguments = Bundle IDs to Quit (multiple can be specified)
5 | #
6 | # Created by Pico Mitchell (of Free Geek) on 9/13/22 (updated on 1/9/23).
7 | #
8 | # MIT License
9 | #
10 | # Copyright (c) 2022 Free Geek
11 | #
12 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"),
13 | # to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
14 | # and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
15 | #
16 | # The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
17 | #
18 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20 | # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 | #
22 |
23 | if [ "$#" -eq '0' ]; then
24 | >&2 echo 'Quit App by Bundle ID ERROR: At least one Bundle ID must be specified.'
25 | return 1
26 | fi
27 |
28 | # Based On: https://github.com/t-lark/Auto-Update/blob/master/app_quitter.py#L179-L190 (Copyright (c) 2019 Snowflake Inc. Licensed under the Apache License, Version 2.0)
29 | /usr/bin/osascript -l 'JavaScript' -e '
30 | "use strict"
31 | ObjC.import("AppKit")
32 |
33 | function run(argv) {
34 | for (const thisBundleID of argv) {
35 | const runningAppsForBundleID = $.NSRunningApplication.runningApplicationsWithBundleIdentifier(thisBundleID).js
36 |
37 | for (const thisRunningApp of runningAppsForBundleID) { // An array is always returned, so must iterate all NSRunningApplication objects.
38 | thisRunningApp.terminate // First tell the app to quit itself gracefully.
39 |
40 | for (let waitForQuitSeconds = 0; waitForQuitSeconds < 6; waitForQuitSeconds ++) { // Wait for UP TO 3 seconds for the app to quit.
41 | delay(0.5) // Wait in half seconds so that we can be done quickly when the app quits itself gracefully.
42 | if (thisRunningApp.terminated)
43 | break
44 | }
45 |
46 | if (!thisRunningApp.terminated) // If app has not quit gracefully after 3 seconds, force quit it.
47 | thisRunningApp.forceTerminate
48 | }
49 | }
50 | }
51 | ' -- "$@" 2> /dev/null
52 | }
53 |
54 | quit_apps_by_bundle_id "$@"
55 |
--------------------------------------------------------------------------------
/Production Scripts/Free Geek Login Progress/Source/Free Geek Login Progress.applescript:
--------------------------------------------------------------------------------
1 | -- By: Pico Mitchell
2 | -- For: MacLand @ Free Geek
3 | --
4 | -- MIT License
5 | --
6 | -- Copyright (c) 2021 Free Geek
7 | --
8 | -- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"),
9 | -- to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
10 | -- and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
11 | --
12 | -- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
13 | --
14 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
16 | -- WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
17 | --
18 |
19 | -- Version: 2023.2.9-1
20 |
21 | -- Build Flag: LSUIElement
22 | -- Build Flag: IncludeSignedLauncher
23 |
24 | use AppleScript version "2.7"
25 | use scripting additions
26 | use framework "AppKit"
27 |
28 | set currentBundleIdentifier to "UNKNOWN"
29 |
30 | try
31 | set infoPlistPath to ((POSIX path of (path to me)) & "Contents/Info.plist")
32 | ((infoPlistPath as POSIX file) as alias)
33 |
34 | set intendedAppName to "Free Geek Login Progress" -- Hardcode intended App name because Name or Bundle Identifier changes should not be done lightly or accidentally.
35 |
36 | try
37 | do shell script ("/usr/libexec/PlistBuddy -c 'Print :FGBuiltByMacLandScriptBuilder' " & (quoted form of infoPlistPath))
38 | ((((POSIX path of (path to me)) & "Contents/MacOS/" & intendedAppName) as POSIX file) as alias)
39 | on error
40 | try
41 | activate
42 | end try
43 | display alert "
44 | “" & (name of me) & "” must be built by the “MacLand Script Builder” script." buttons {"Quit"} default button 1 as critical
45 | quit
46 | delay 10
47 | end try
48 |
49 | set AppleScript's text item delimiters to "-"
50 | set intendedBundleIdentifier to ("org.freegeek." & ((words of intendedAppName) as text))
51 | set currentBundleIdentifier to ((do shell script ("/usr/libexec/PlistBuddy -c 'Print :CFBundleIdentifier' " & (quoted form of infoPlistPath))) as text)
52 | if (currentBundleIdentifier is not equal to intendedBundleIdentifier) then error "“" & (name of me) & "” does not have the correct Bundle Identifier.
53 |
54 |
55 | Current Bundle Identifier:
56 | " & currentBundleIdentifier & "
57 |
58 | Intended Bundle Identifier:
59 | " & intendedBundleIdentifier
60 | on error checkInfoPlistError
61 | if (checkInfoPlistError does not start with "Can’t make file") then
62 | try
63 | activate
64 | end try
65 | display alert checkInfoPlistError buttons {"Quit"} default button 1 as critical
66 | quit
67 | delay 10
68 | end if
69 | end try
70 |
71 | try
72 | (current application's NSApp's mainMenu's removeAllItems()) -- Remove all menu items so shortcuts like Command+E to "Edit Script" cannot be used to disrupt anything.
73 | end try
74 |
75 | set isRunningAtLoginWindow to false
76 | try
77 | set isRunningAtLoginWindow to ((do shell script "launchctl managername") is equal to "LoginWindow")
78 | end try
79 |
80 | set isResetting to ((POSIX path of (path to me)) contains "/fg-snapshot-reset/")
81 |
82 | set logPath to "/Users/Shared/Build Info/Prepare OS Log.txt"
83 | if (isResetting) then set logPath to "/Users/Shared/fg-snapshot-reset/log.txt"
84 |
85 | set progress total steps to -1
86 | set progress completed steps to 0
87 |
88 | set doNotDisturbNote to "🚫 DO NOT DISTURB THIS MAC WHILE IT IS BEING CUSTOMIZED"
89 |
90 | if (isResetting) then
91 | set progress description to "🧹 Resetting this Mac…"
92 | set doNotDisturbNote to "🚫 DO NOT DISTURB THIS MAC WHILE IT IS BEING RESET"
93 | else
94 | set progress description to "🚧 Customizing this Mac…"
95 | end if
96 |
97 | set progress additional description to ("
98 | " & doNotDisturbNote)
99 |
100 | try
101 | repeat with thisWindow in (current application's NSApp's |windows|())
102 | if (thisWindow's isVisible() is true) then
103 | if (((thisWindow's title()) as text) is equal to (name of me)) then
104 | repeat with thisProgressWindowSubView in ((thisWindow's contentView())'s subviews())
105 | if (((thisProgressWindowSubView's className()) as text) is equal to "NSProgressIndicator") then
106 | if (isResetting) then
107 | (thisWindow's setTitle:"Free Geek Reset Progress")
108 | else
109 | (thisWindow's setTitle:"Free Geek Customizations Progress")
110 | end if
111 |
112 | -- Set Style Mask to ONLY be Titled, which make it not minimizable or resizable and hides all the titlebar buttons.
113 | (thisWindow's setStyleMask:(current application's NSWindowStyleMaskTitled as integer)) -- MUST be "as integer" instead of "as number" for ObjC-bridge casting to not throw an exception.
114 |
115 | -- Also do not want window to be movable so that it stays over the login fields.
116 | if (isRunningAtLoginWindow) then (thisWindow's setMovable:false) -- Only do this if at the login window to make debugging easier when running in OS.
117 |
118 | -- Make the window wider so that the width doesn't need to be expanded automatically when long log entries get added, which makes re-centering look funky.
119 | set thisWindowSize to (item 2 of (thisWindow's frame()))
120 | (thisWindow's setContentSize:{650, (item 2 of thisWindowSize)}) -- Largest width seen was 630 for the log line deleting Secure Token References with the UUID.
121 |
122 | -- Center the window so it's in a nice spot on the screen and will leave a lot of room below for the log.
123 | -- The window will also keep being re-centered as the log grows so that the most contents will always stay visible no matter what the screen size is.
124 | -- Previously was just moving the window up 100 points to hide the login icon/fields, but that didn't work great as I added more logging and the window could grow to go below the bottom of the screen.
125 | -- So now, when the log is short the login icon/fields may be visible but should get covered as the log grows throughout the process.
126 | (thisWindow's |center|())
127 |
128 | -- References for Visibility at Login Screen:
129 | -- https://developer.apple.com/library/archive/samplecode/PreLoginAgents/Listings/PreLoginAgentCocoa_AppDelegate_m.html#//apple_ref/doc/uid/DTS10004414-PreLoginAgentCocoa_AppDelegate_m-DontLinkElementID_5
130 | -- https://bitbucket.org/twocanoes/macdeploystick/src/a7989eddae93d3339b54e30356da0c6ff13fd795/first-run-install/Scripts/com.twocanoes.mds/LoginLog.app/Contents/Resources/LLLogWindowController.py#lines-130
131 | -- https://bitbucket.org/twocanoes/loginlog/src/6a15de70660a7d93b9c9bcbf3ed7efd85fb57d17/LoginLog/LLLogWindowController.swift#lines-108
132 | (thisWindow's setCanBecomeVisibleWithoutLogin:true)
133 | (thisWindow's setLevel:2.147483647E+9) -- The highest defined window level is "kCGMaximumWindowLevelKey" which is equal to "2147483631" (https://michelf.ca/blog/2016/choosing-window-level/). We are setting an even higher level of "2147483647" which is the signed 32-bit interger max which seems to be the highest possible level since any higher and the value appears to roll over and no longer be topmost.
134 | (thisWindow's orderFrontRegardless()) -- This seemed to not be necessary on macOS 11 Big Sur and older, but I found that it is very necessary on macOS 12 Monterey and newer since this (or presumably any) app cannot actually activate itself to become frontmost.
135 | else if ((((thisProgressWindowSubView's className()) as text) is equal to "NSButton") and ((thisProgressWindowSubView's title() as text) is equal to "Stop")) then
136 | if (isRunningAtLoginWindow) then (thisProgressWindowSubView's setEnabled:false) -- Only do this if at the login window to make debugging easier when running in OS.
137 | end if
138 | end repeat
139 | end if
140 | end if
141 | end repeat
142 | end try
143 |
144 | repeat
145 | try
146 | activate -- On macOS 12 Monterey and newer, it seems that this (or presumably any) app cannot activate itself at the login window using either AppleScript "activate" or ObjC "activateIgnoringOtherApps" (but still "activate" anyway for older macOS versions).
147 | -- It appears that "com.apple.SecurityAgent" is always the frontmost application at the login window on macOS 12 Monterey and newer and nothing I've tried can change that.
148 | -- The failure of this app to be able to activate itself on macOS 12 Monterey and newer makes having the window "orderFrontRegardless" (done above and below here) very important so the window is visible at all, but it won't be the focused key window since this app isn't frontmost.
149 | end try
150 |
151 | if ((progress completed steps) is equal to 0) then
152 | try
153 | ((logPath as POSIX file) as alias)
154 |
155 | set logOutput to (do shell script ("cat " & (quoted form of logPath)))
156 |
157 | considering case -- Make sure the following check for an error is case senstive to not have any false matched warning messages.
158 | if (logOutput contains " ERROR:") then -- Also check for the TAB before "ERROR:" to be sure this is one of our error messages and not part of a warning message.
159 | set progress total steps to 1
160 | set progress completed steps to 1
161 |
162 | if (isResetting) then
163 | set progress description to "❌ Error Occurred While Resetting this Mac"
164 | else
165 | set progress description to "❌ Error Occurred While Customizing this Mac"
166 | end if
167 |
168 | set progress additional description to ("
169 | ‼️ PLEASE INFORM AND DELIVER THIS MAC TO FREE GEEK I.T.
170 |
171 | " & logOutput)
172 | else
173 | set progress additional description to ("
174 | " & doNotDisturbNote & "
175 |
176 | " & logOutput)
177 | end if
178 | end considering
179 | end try
180 |
181 | if ((progress completed steps) is equal to 0) then -- Do not check for completion if an error was detected in the log.
182 | try
183 | -- The LaunchDaemon being deleted indicates successful completion.
184 | if (isResetting) then
185 | (("/Library/LaunchDaemons/org.freegeek.fg-snapshot-reset.plist" as POSIX file) as alias)
186 | else
187 | (("/Library/LaunchDaemons/org.freegeek.fg-install-packages.plist" as POSIX file) as alias)
188 | end if
189 | on error
190 | set progress total steps to 1
191 | set progress completed steps to 1
192 |
193 | if (isResetting) then
194 | set progress description to "✅ Successfully Reset this Mac"
195 | set progress additional description to (progress additional description & "
196 |
197 | ⤵️ SHUTTING DOWN THIS MAC")
198 | else
199 | set progress description to "✅ Successfully Customized this Mac"
200 | set progress additional description to (progress additional description & "
201 |
202 | 🔄 REBOOTING THIS MAC")
203 | end if
204 | end try
205 | end if
206 |
207 | try
208 | repeat with thisWindow in (current application's NSApp's |windows|())
209 | if (thisWindow's isVisible() is true) then
210 | set thisWindowTitle to ((thisWindow's title()) as text)
211 | if ((thisWindowTitle is equal to (name of me)) or (thisWindowTitle ends with " Progress")) then
212 | ((thisWindow's contentView())'s display()) -- Force display before re-centering since sometimes it takes a moment for the window contents to be updated on their own which can cause the centering to happen before the contents update.
213 | (thisWindow's |center|()) -- Keep re-centering window so the most contents will always be displayed no matter how long the log gets and what the screen size is.
214 | (thisWindow's orderFrontRegardless()) -- Also keep ordering front just in case.
215 | end if
216 | end if
217 | end repeat
218 | end try
219 | end if
220 |
221 | delay 0.25
222 | end repeat
223 |
--------------------------------------------------------------------------------
/Production Scripts/Free Geek Snapshot Helper/Source/Free Geek Snapshot Helper.applescript:
--------------------------------------------------------------------------------
1 | -- By: Pico Mitchell
2 | -- For: MacLand @ Free Geek
3 | --
4 | -- MIT License
5 | --
6 | -- Copyright (c) 2021 Free Geek
7 | --
8 | -- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"),
9 | -- to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
10 | -- and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
11 | --
12 | -- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
13 | --
14 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
16 | -- WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
17 | --
18 |
19 | -- Version: 2023.7.7-3
20 |
21 | -- Build Flag: LSUIElement
22 |
23 | use AppleScript version "2.7"
24 | use scripting additions
25 |
26 | set currentBundleIdentifier to "UNKNOWN"
27 |
28 | try
29 | set infoPlistPath to ((POSIX path of (path to me)) & "Contents/Info.plist")
30 | ((infoPlistPath as POSIX file) as alias)
31 |
32 | set intendedAppName to "Free Geek Snapshot Helper" -- Hardcode intended App name because Name or Bundle Identifier changes should not be done lightly or accidentally.
33 |
34 | try
35 | do shell script ("/usr/libexec/PlistBuddy -c 'Print :FGBuiltByMacLandScriptBuilder' " & (quoted form of infoPlistPath))
36 | ((((POSIX path of (path to me)) & "Contents/MacOS/" & intendedAppName) as POSIX file) as alias)
37 | on error
38 | try
39 | activate
40 | end try
41 | display alert "
42 | “" & (name of me) & "” must be built by the “MacLand Script Builder” script." buttons {"Quit"} default button 1 as critical
43 | quit
44 | delay 10
45 | end try
46 |
47 | set AppleScript's text item delimiters to "-"
48 | set intendedBundleIdentifier to ("org.freegeek." & ((words of intendedAppName) as text))
49 | set currentBundleIdentifier to ((do shell script ("/usr/libexec/PlistBuddy -c 'Print :CFBundleIdentifier' " & (quoted form of infoPlistPath))) as text)
50 | if (currentBundleIdentifier is not equal to intendedBundleIdentifier) then error "“" & (name of me) & "” does not have the correct Bundle Identifier.
51 |
52 |
53 | Current Bundle Identifier:
54 | " & currentBundleIdentifier & "
55 |
56 | Intended Bundle Identifier:
57 | " & intendedBundleIdentifier
58 | on error checkInfoPlistError
59 | if (checkInfoPlistError does not start with "Can’t make file") then
60 | try
61 | activate
62 | end try
63 | display alert checkInfoPlistError buttons {"Quit"} default button 1 as critical
64 | quit
65 | delay 10
66 | end if
67 | end try
68 |
69 | try
70 | set mainScptPath to ((POSIX path of (path to me)) & "Contents/Resources/Scripts/main.scpt")
71 | ((mainScptPath as POSIX file) as alias)
72 | do shell script "osadecompile " & (quoted form of mainScptPath)
73 | error "
74 | “" & (name of me) & "” must be exported as a Run-Only Script."
75 | on error checkReadOnlyErrorMessage
76 | if ((checkReadOnlyErrorMessage does not contain "errOSASourceNotAvailable") and (checkReadOnlyErrorMessage does not start with "Can’t make file")) then
77 | try
78 | activate
79 | end try
80 | display alert checkReadOnlyErrorMessage buttons {"Quit"} default button 1 as critical
81 | quit
82 | delay 10
83 | end if
84 | end try
85 |
86 |
87 | global adminUsername, adminPassword, lastDoShellScriptAsAdminAuthDate -- Needs to be accessible in doShellScriptAsAdmin function.
88 | set lastDoShellScriptAsAdminAuthDate to 0
89 |
90 | set adminUsername to "fg-admin"
91 | set adminPassword to "[MACLAND SCRIPT BUILDER WILL REPLACE THIS PLACEHOLDER WITH OBFUSCATED ADMIN PASSWORD]"
92 |
93 | set demoUsername to "fg-demo"
94 |
95 |
96 | if (((short user name of (system info)) is equal to demoUsername) and ((POSIX path of (path to me)) is equal to ("/Users/" & demoUsername & "/Applications/" & (name of me) & ".app/"))) then
97 | set freeGeekUpdaterAppPath to ("/Users/" & demoUsername & "/Applications/Free Geek Updater.app")
98 | try
99 | ((freeGeekUpdaterAppPath as POSIX file) as alias)
100 |
101 | if (application freeGeekUpdaterAppPath is running) then -- Quit if Updater is running so that this app can be updated if needed.
102 | quit
103 | delay 10
104 | end if
105 | end try
106 |
107 | try
108 | (("/Users/Shared/.fg-snapshot-preserver" as POSIX file) as alias)
109 |
110 | set shouldShutDownAfterError to false
111 | try
112 | (("/Users/Shared/.fgResetSnapshotCreated" as POSIX file) as alias)
113 |
114 | set systemVersion to (system version of (system info))
115 | considering numeric strings
116 | set isBigSurOrNewer to (systemVersion ≥ "11.0")
117 | end considering
118 |
119 | if (isBigSurOrNewer) then
120 | try
121 | -- Do not bother try re-mounting if it's already mounted.
122 | (("/Users/Shared/.fg-snapshot-preserver/mount/Users/Shared/fg-snapshot-reset" as POSIX file) as alias)
123 | try
124 | doShellScriptAsAdmin("echo \"$(date '+%D %T') Snapshot Helper: Reset Snapshot Already Mounted\" >> '/Users/Shared/.fg-snapshot-preserver/log.txt'")
125 | end try
126 | on error
127 | set resetSnapshotName to (do shell script "head -1 /Users/Shared/.fgResetSnapshotCreated")
128 |
129 | try
130 | if (resetSnapshotName starts with "com.apple.TimeMachine") then
131 | try
132 | (("/Users/Shared/.fg-snapshot-preserver/mount" as POSIX file) as alias)
133 | on error
134 | try
135 | -- Needs admin privileges since root owns ".fg-snapshot-preserver" folder.
136 | doShellScriptAsAdmin("mkdir '/Users/Shared/.fg-snapshot-preserver/mount'")
137 | end try
138 | end try
139 |
140 | try
141 | -- But the mount folder needs to be writeable by demoUsername or mounting the snapshot will fail (even when using administrator privileges).
142 | doShellScriptAsAdmin("chown " & demoUsername & " '/Users/Shared/.fg-snapshot-preserver/mount'")
143 | end try
144 |
145 | try
146 | -- Mounting the reset Snapshot will prevent macOS from deleting it after 24 hours: https://eclecticlight.co/2021/03/28/last-week-on-my-mac-macos-at-20-apfs-at-4/#comment-59001
147 | do shell script ("bash -c " & (quoted form of ("mount_apfs -o rdonly,nobrowse -s " & (quoted form of resetSnapshotName) & " \"$(/usr/libexec/PlistBuddy -c 'Print :DeviceNode' /dev/stdin <<< \"$(diskutil info -plist '/System/Volumes/Data')\")\" '/Users/Shared/.fg-snapshot-preserver/mount'")))
148 | try
149 | doShellScriptAsAdmin("echo \"$(date '+%D %T') Snapshot Helper: Successfully Mounted Reset Snapshot\" >> '/Users/Shared/.fg-snapshot-preserver/log.txt'")
150 | end try
151 | on error mountErrorMessage number mountErrorNumber
152 | set snapshotMountError to ("FAILED to Mount Reset Snapshot (Error Code " & (mountErrorNumber as text) & ": " & mountErrorMessage & ")")
153 | try
154 | doShellScriptAsAdmin("echo \"$(date '+%D %T') Snapshot Helper: " & snapshotMountError & "\" >> '/Users/Shared/.fg-snapshot-preserver/log.txt'")
155 | end try
156 |
157 | error snapshotMountError
158 | end try
159 | else
160 | set snapshotNameError to ("Invalid Reset Snapshot Name (" & resetSnapshotName & ")")
161 | try
162 | doShellScriptAsAdmin("echo \"$(date '+%D %T') Snapshot Helper: " & snapshotNameError & "\" >> '/Users/Shared/.fg-snapshot-preserver/log.txt'")
163 | end try
164 |
165 | error snapshotNameError
166 | end if
167 | on error snapshotErrorMessage
168 | try
169 | activate
170 | end try
171 | try
172 | do shell script "afplay /System/Library/Sounds/Basso.aiff > /dev/null 2>&1 &"
173 | end try
174 | display alert ("CRITICAL “" & (name of me) & "” ERROR:
175 |
176 | " & snapshotErrorMessage) message "This should not have happened, please inform and deliver this Mac to Free Geek I.T. for further research." buttons {"Shut Down"} default button 1 as critical
177 |
178 | set shouldShutDownAfterError to true
179 | end try
180 | end try
181 | else
182 | try
183 | doShellScriptAsAdmin("echo \"$(date '+%D %T') Snapshot Helper: Not Mounting Reset Snapshot on Catalina\" >> '/Users/Shared/.fg-snapshot-preserver/log.txt'")
184 | end try
185 | end if
186 | on error
187 | set resetSnapshotName to "UNKNOWN SNAPSHOT NAME"
188 | set resetSnapshotLostReason to "UNKNOWN LOST REASON"
189 | try
190 | (("/Users/Shared/.fgResetSnapshotLost" as POSIX file) as alias)
191 | try
192 | set resetSnapshotLostReason to (do shell script "tail -1 /Users/Shared/.fgResetSnapshotLost")
193 | end try
194 | try
195 | set possibleResetSnapshotName to (do shell script "head -1 /Users/Shared/.fgResetSnapshotLost") -- ".fgResetSnapshotLost" could only have one line if the ".fgResetSnapshotCreated" file was manually deleted (which shouldn't happene except in my testing).
196 | if (possibleResetSnapshotName is not equal to resetSnapshotLostReason) then set resetSnapshotName to possibleResetSnapshotName
197 | end try
198 | end try
199 |
200 | repeat -- dialogs timeout when screen is asleep or locked (just in case)
201 | set isAwake to true
202 | try
203 | set isAwake to ((run script "ObjC.import('CoreGraphics'); $.CGDisplayIsActive($.CGMainDisplayID())" in "JavaScript") is equal to 1)
204 | end try
205 |
206 | set isUnlocked to true
207 | try
208 | set isUnlocked to ((do shell script ("bash -c " & (quoted form of "/usr/libexec/PlistBuddy -c 'Print :IOConsoleUsers:0:CGSSessionScreenIsLocked' /dev/stdin <<< \"$(ioreg -ac IORegistryEntry -k IOConsoleUsers -d 1)\""))) is not equal to "true")
209 | end try
210 |
211 | if (isAwake and isUnlocked) then
212 | exit repeat
213 | else
214 | delay 1
215 | end if
216 | end repeat
217 |
218 | try
219 | activate
220 | end try
221 | try
222 | do shell script "afplay /System/Library/Sounds/Basso.aiff > /dev/null 2>&1 &"
223 | end try
224 | display alert ("CRITICAL “" & (name of me) & "” ERROR:
225 |
226 | Reset Snapshot Has Been Lost
227 |
228 | " & resetSnapshotLostReason & "
229 |
230 | This Mac CANNOT BE SOLD since it cannot be reset.") message ("
231 | Reset Snapshot Name: " & resetSnapshotName & "
232 |
233 | This should not have happened, please inform and deliver this Mac to Free Geek I.T. for further research.") buttons {"Shut Down"} default button 1 as critical
234 |
235 | set shouldShutDownAfterError to true
236 | end try
237 |
238 | try
239 | doShellScriptAsAdmin("rm -rf '/Users/Shared/.fg-snapshot-preserver/.launchedSnapshotHelper'")
240 | end try
241 |
242 | if (shouldShutDownAfterError) then
243 | tell application id "com.apple.systemevents" to shut down with state saving preference
244 | quit
245 | delay 10
246 | end if
247 | end try
248 | else
249 | try
250 | (("/Users/Shared/.fg-snapshot-preserver" as POSIX file) as alias)
251 |
252 | try
253 | doShellScriptAsAdmin("echo \"$(date '+%D %T') Snapshot Helper: NOT RUNNING Because Not Logged In or Not Installed in Correct Location\" >> '/Users/Shared/.fg-snapshot-preserver/log.txt'")
254 | end try
255 | on error
256 | try
257 | activate
258 | end try
259 | display alert "Cannot Run “" & (name of me) & "”" message "“" & (name of me) & "” must be installed at
260 | “/Users/" & demoUsername & "/Applications/” and run from the “" & demoUsername & "” user account." buttons {"Quit"} default button 1 as critical
261 | end try
262 | end if
263 |
264 | on doShellScriptAsAdmin(command)
265 | -- "do shell script with administrator privileges" caches authentication for 5 minutes: https://developer.apple.com/library/archive/technotes/tn2065/_index.html#//apple_ref/doc/uid/DTS10003093-CH1-TNTAG1-HOW_DO_I_GET_ADMINISTRATOR_PRIVILEGES_FOR_A_COMMAND_ & https://developer.apple.com/library/archive/releasenotes/AppleScript/RN-AppleScript/RN-10_4/RN-10_4.html#//apple_ref/doc/uid/TP40000982-CH104-SW10
266 | -- And, it takes reasonably longer to run "do shell script with administrator privileges" when credentials are passed vs without.
267 | -- In testing, 100 iteration with credentials took about 30 seconds while 100 interations without credentials after authenticated in advance took only 2 seconds.
268 | -- So, this function makes it easy to call "do shell script with administrator privileges" while only passing credentials when needed.
269 | -- Also, from testing, this 5 minute credential caching DOES NOT seem to be affected by any custom "sudo" timeout set in the sudoers file.
270 | -- And, from testing, unlike "sudo" the timeout DOES NOT keep extending from the last "do shell script with administrator privileges" without credentials but only from the last time credentials were passed.
271 | -- To be safe, "do shell script with administrator privileges" will be re-authenticated with the credentials every 4.5 minutes.
272 | -- NOTICE: "do shell script" calls are intentionally NOT in "try" blocks since detecting and catching those errors may be critical to the code calling the "doShellScriptAsAdmin" function.
273 |
274 | set currentDate to (current date)
275 | if ((lastDoShellScriptAsAdminAuthDate is equal to 0) or (currentDate ≥ (lastDoShellScriptAsAdminAuthDate + 270))) then -- 270 seconds = 4.5 minutes.
276 | set commandOutput to (do shell script command user name adminUsername password adminPassword with administrator privileges)
277 | set lastDoShellScriptAsAdminAuthDate to currentDate -- Set lastDoShellScriptAsAdminAuthDate to date *BEFORE* command was run since the command itself could have updated the date and the 5 minute timeout started when the command started, not when it finished.
278 | else
279 | set commandOutput to (do shell script command with prompt "This “" & (name of me) & "” password prompt should not have been displayed.
280 |
281 | Please inform Free Geek I.T. that you saw this password prompt.
282 |
283 | You can just press “Cancel” below to continue." with administrator privileges)
284 | end if
285 |
286 | return commandOutput
287 | end doShellScriptAsAdmin
288 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # macOS Testing and Deployment Scripts
2 |
3 | These are the scripts used by Free Geek to test and deploy macOS.
4 |
--------------------------------------------------------------------------------
/Testing Scripts/Camera Test/Source/Camera Test.applescript:
--------------------------------------------------------------------------------
1 | -- By: Pico Mitchell
2 | -- For: MacLand @ Free Geek
3 | --
4 | -- MIT License
5 | --
6 | -- Copyright (c) 2021 Free Geek
7 | --
8 | -- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"),
9 | -- to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
10 | -- and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
11 | --
12 | -- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
13 | --
14 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
16 | -- WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
17 | --
18 |
19 | -- Version: 2023.7.7-3
20 |
21 | -- App Icon is “Movie Camera” from Twemoji (https://twemoji.twitter.com/) by Twitter (https://twitter.com)
22 | -- Licensed under CC-BY 4.0 (https://creativecommons.org/licenses/by/4.0/)
23 |
24 | use AppleScript version "2.7"
25 | use scripting additions
26 |
27 | repeat -- dialogs timeout when screen is asleep or locked (just in case)
28 | set isAwake to true
29 | try
30 | set isAwake to ((run script "ObjC.import('CoreGraphics'); $.CGDisplayIsActive($.CGMainDisplayID())" in "JavaScript") is equal to 1)
31 | end try
32 |
33 | set isUnlocked to true
34 | try
35 | set isUnlocked to ((do shell script ("bash -c " & (quoted form of "/usr/libexec/PlistBuddy -c 'Print :IOConsoleUsers:0:CGSSessionScreenIsLocked' /dev/stdin <<< \"$(ioreg -ac IORegistryEntry -k IOConsoleUsers -d 1)\""))) is not equal to "true")
36 | end try
37 |
38 | if (isAwake and isUnlocked) then
39 | exit repeat
40 | else
41 | delay 1
42 | end if
43 | end repeat
44 |
45 | try
46 | set infoPlistPath to ((POSIX path of (path to me)) & "Contents/Info.plist")
47 | ((infoPlistPath as POSIX file) as alias)
48 |
49 | set intendedAppName to "Camera Test" -- Hardcode intended App name because Name or Bundle Identifier changes should not be done lightly or accidentally.
50 |
51 | try
52 | do shell script ("/usr/libexec/PlistBuddy -c 'Print :FGBuiltByMacLandScriptBuilder' " & (quoted form of infoPlistPath))
53 | ((((POSIX path of (path to me)) & "Contents/MacOS/" & intendedAppName) as POSIX file) as alias)
54 | on error
55 | try
56 | activate
57 | end try
58 | display alert "
59 | “" & (name of me) & "” must be built by the “MacLand Script Builder” script." buttons {"Quit"} default button 1 as critical
60 | quit
61 | delay 10
62 | end try
63 |
64 | set AppleScript's text item delimiters to "-"
65 | set intendedBundleIdentifier to ("org.freegeek." & ((words of intendedAppName) as text))
66 | set currentBundleIdentifier to ((do shell script ("/usr/libexec/PlistBuddy -c 'Print :CFBundleIdentifier' " & (quoted form of infoPlistPath))) as text)
67 | if (currentBundleIdentifier is not equal to intendedBundleIdentifier) then error "“" & (name of me) & "” does not have the correct Bundle Identifier.
68 |
69 |
70 | Current Bundle Identifier:
71 | " & currentBundleIdentifier & "
72 |
73 | Intended Bundle Identifier:
74 | " & intendedBundleIdentifier
75 | on error checkInfoPlistError
76 | if (checkInfoPlistError does not start with "Can’t make file") then
77 | try
78 | activate
79 | end try
80 | display alert checkInfoPlistError buttons {"Quit"} default button 1 as critical
81 | quit
82 | delay 10
83 | end if
84 | end try
85 |
86 | try
87 | set mainScptPath to ((POSIX path of (path to me)) & "Contents/Resources/Scripts/main.scpt")
88 | ((mainScptPath as POSIX file) as alias)
89 | do shell script "osadecompile " & (quoted form of mainScptPath)
90 | error "
91 | “" & (name of me) & "” must be exported as a Run-Only Script."
92 | on error checkReadOnlyErrorMessage
93 | if ((checkReadOnlyErrorMessage does not contain "errOSASourceNotAvailable") and (checkReadOnlyErrorMessage does not start with "Can’t make file")) then
94 | activate
95 | display alert checkReadOnlyErrorMessage buttons {"Quit"} default button 1 as critical
96 | quit
97 | delay 10
98 | end if
99 | end try
100 |
101 |
102 | set freeGeekUpdaterAppPath to "/Applications/Free Geek Updater.app"
103 | set freeGeekUpdaterIsRunning to false
104 | try
105 | ((freeGeekUpdaterAppPath as POSIX file) as alias)
106 | set freeGeekUpdaterIsRunning to (application freeGeekUpdaterAppPath is running)
107 | end try
108 |
109 | set adminUsername to "Staff"
110 | set adminPassword to "[MACLAND SCRIPT BUILDER WILL REPLACE THIS PLACEHOLDER WITH OBFUSCATED ADMIN PASSWORD]"
111 |
112 | set buildInfoPath to ((POSIX path of (path to shared documents folder)) & "Build Info/")
113 |
114 | try
115 | (((buildInfoPath & ".fgSetupSkipped") as POSIX file) as alias)
116 |
117 | try
118 | do shell script ("mkdir " & (quoted form of buildInfoPath))
119 | end try
120 | try
121 | set AppleScript's text item delimiters to "-"
122 | do shell script ("touch " & (quoted form of (buildInfoPath & ".fgLaunchAfterSetup-org.freegeek." & ((words of (name of me)) as text)))) user name adminUsername password adminPassword with administrator privileges
123 | end try
124 |
125 | if (not freeGeekUpdaterIsRunning) then
126 | try
127 | -- For some reason, on Big Sur, apps are not opening unless we specify "-n" to "Open a new instance of the application(s) even if one is already running." All scripts have LSMultipleInstancesProhibited to this will not actually ever open a new instance.
128 | do shell script "open -na '/Applications/Test Boot Setup.app'"
129 | end try
130 | end if
131 |
132 | quit
133 | delay 10
134 | end try
135 |
136 | if (freeGeekUpdaterIsRunning) then -- Quit if Updater is running so that this app can be updated if needed.
137 | quit
138 | delay 10
139 | end if
140 |
141 |
142 | set systemVersion to (system version of (system info))
143 | considering numeric strings
144 | set isMojaveOrNewer to (systemVersion ≥ "10.14")
145 | set isCatalinaOrNewer to (systemVersion ≥ "10.15")
146 | end considering
147 |
148 | if (isMojaveOrNewer) then
149 | try
150 | tell application id "com.apple.QuickTimePlayerX" to every window -- To prompt for Automation access on Mojave
151 | on error automationAccessErrorMessage number automationAccessErrorNumber
152 | if (automationAccessErrorNumber is equal to -1743) then
153 | try
154 | tell application id "com.apple.systempreferences" to activate
155 | end try
156 | try
157 | open location "x-apple.systempreferences:com.apple.preference.security?Privacy_Automation" -- The "Privacy_Automation" anchor is not exposed/accessible via AppleScript, but can be accessed via URL Scheme.
158 | end try
159 | try
160 | activate
161 | end try
162 | try
163 | display dialog "“" & (name of me) & "” must be allowed to control and perform actions in “QuickTime Player” to be able to function.
164 |
165 |
166 | USE THE FOLLOWING STEPS TO FIX THIS ISSUE:
167 |
168 | • Open the “System Preferences” application.
169 |
170 | • Click the “Security & Privacy” preference pane.
171 |
172 | • Select the “Privacy” tab.
173 |
174 | • Select “Automation” in the source list on the left.
175 |
176 | • Find “" & (name of me) & "” in the list on the right and turn on the “QuickTime Player” checkbox underneath it.
177 |
178 | • Relaunch “" & (name of me) & "” (using the button below)." buttons {"Quit", "Relaunch “" & (name of me) & "”"} cancel button 1 default button 2 with title (name of me) with icon caution
179 | try
180 | do shell script "osascript -e 'delay 0.5' -e 'repeat while (application \"" & (POSIX path of (path to me)) & "\" is running)' -e 'delay 0.5' -e 'end repeat' -e 'do shell script \"open -na \\\"" & (POSIX path of (path to me)) & "\\\"\"' > /dev/null 2>&1 &"
181 | end try
182 | end try
183 | quit
184 | delay 10
185 | end if
186 | end try
187 | try
188 | with timeout of 1 second
189 | tell application id "com.apple.QuickTimePlayerX" to quit
190 | end timeout
191 | end try
192 | end if
193 |
194 |
195 | set cameraTestDuration to 10
196 | set testCount to 0
197 |
198 | try
199 | repeat
200 | set cameraTestButtons to {"Quit", "Test Camera"}
201 | if (testCount ≥ 1) then set cameraTestButtons to {"Test Camera Again", "Done"}
202 |
203 | set shouldTestCamera to false
204 | try
205 | activate
206 | end try
207 | try
208 | display dialog " 🎥 Camera Test will open a camera feed in
209 | full screen and keep it open for " & cameraTestDuration & " seconds.
210 |
211 | 👉 YOU DO NOT NEED TO RECORD THE VIDEO!
212 |
213 | 👋 Wave your hands in front of the camera to
214 | make sure the camera feed updates properly.
215 |
216 | 👀 Look around the entire image to make sure
217 | the camera feed is crisp, clear, and bright.
218 | Also, make sure there is no darkness around
219 | the edge as well as no spots or artifacts.
220 |
221 | ⏱ After " & cameraTestDuration & " seconds, the camera feed will
222 | be closed and this window will open again.
223 |
224 |
225 | ✅ CAMERA TEST PASSED IF:
226 | ⁃ The image is crisp, clear, and bright.
227 | ⁃ There is no dark edge around the image.
228 | ⁃ There are no spots or artifacts in the image.
229 |
230 | ❌ CAMERA TEST FAILED IF:
231 | ⁃ The image is blurry or dim.
232 | ⁃ There is a dark edge around the image.
233 | ⁃ There are any spots or artifacts in the image.
234 |
235 |
236 | 👉 CONSULT INSTRUCTOR IF CAMERA TEST FAILS ‼️" buttons cameraTestButtons cancel button 1 default button 2 with title "Camera Test"
237 | if ((last text item of cameraTestButtons) is equal to "Test Camera") then set shouldTestCamera to true
238 | on error
239 | if ((first text item of cameraTestButtons) is equal to "Test Camera Again") then set shouldTestCamera to true
240 | end try
241 |
242 | if (shouldTestCamera) then
243 | tell application id "com.apple.QuickTimePlayerX"
244 | try
245 | activate
246 | end try
247 | delay 1
248 | try
249 | close every window without saving
250 | end try
251 | set newMovieRecording to new movie recording
252 | delay 1
253 | try
254 | if (newMovieRecording is not presenting) then present newMovieRecording
255 | end try
256 | try
257 | activate
258 | end try
259 | end tell
260 | repeat cameraTestDuration times
261 | if (application id "com.apple.QuickTimePlayerX" is not running) then exit repeat
262 | delay 1
263 | end repeat
264 | if (application id "com.apple.QuickTimePlayerX" is running) then
265 | tell application id "com.apple.QuickTimePlayerX"
266 | try
267 | stop newMovieRecording
268 | end try
269 | delay 1
270 | try
271 | close every window without saving
272 | end try
273 | delay 1
274 | try
275 | quit
276 | end try
277 | end tell
278 | end if
279 | set testCount to (testCount + 1)
280 | else
281 | exit repeat
282 | end if
283 | end repeat
284 | end try
285 |
286 | if (testCount ≥ 1) then
287 | try
288 | (("/Applications/Screen Test.app" as POSIX file) as alias)
289 | if (application id ("org.freegeek." & "Screen-Test") is not running) then -- Break up App ID or else build will fail if not found during compilation when app is not installed.
290 | try
291 | activate
292 | end try
293 | display alert "
294 | Would you like to launch “Screen Test”?" buttons {"No", "Yes"} cancel button 1 default button 2 giving up after 15
295 | do shell script "open -na '/Applications/Screen Test.app'"
296 | end if
297 | end try
298 | end if
299 |
--------------------------------------------------------------------------------
/Testing Scripts/Hard Drive Test/Source/Extract Info from DriveDx Report.applescript:
--------------------------------------------------------------------------------
1 | -- By: Pico Mitchell
2 | -- For: MacLand @ Free Geek
3 | --
4 | -- MIT License
5 | --
6 | -- Copyright (c) 2021 Free Geek
7 | --
8 | -- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"),
9 | -- to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
10 | -- and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
11 | --
12 | -- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
13 | --
14 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
16 | -- WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
17 | --
18 |
19 | -- This is just a proof-of-concept for a possible future applet.
20 |
21 | set driveDxReport to read "/Users/Shared/DriveDxReport.txt"
22 |
23 | set AppleScript's text item delimiters to " ###"
24 | repeat with thisDriveDxReportSection in (every text item of driveDxReport)
25 | set thisAdvancedSmartStatus to "UNKNOWN"
26 | set thisOverallHealthRating to "UNKNOWN"
27 | set thisOverallPerformanceRating to "UNKNOWN"
28 | set thisSSDLifetimeLeftIndicator to "UNKNOWN"
29 | set thisIssuesFound to "UNKNOWN"
30 | set thisSerialNumber to "UNKNOWN"
31 | repeat with thisDriveDxReportSectionLine in (paragraphs of thisDriveDxReportSection)
32 | if (((offset of "### " in thisDriveDxReportSectionLine) is equal to 1) or ((offset of "Report Timestamp" in thisDriveDxReportSectionLine) is equal to 1)) then exit repeat
33 | if ((length of thisDriveDxReportSectionLine) is not equal to 0) then
34 | if ((offset of "Advanced SMART Status" in thisDriveDxReportSectionLine) is equal to 1) then set thisAdvancedSmartStatus to (text 40 thru -1 of thisDriveDxReportSectionLine)
35 | if ((offset of "Overall Health Rating" in thisDriveDxReportSectionLine) is equal to 1) then set thisOverallHealthRating to (text 40 thru -1 of thisDriveDxReportSectionLine)
36 | if ((offset of "Overall Performance Rating" in thisDriveDxReportSectionLine) is equal to 1) then set thisOverallPerformanceRating to (text 40 thru -1 of thisDriveDxReportSectionLine)
37 | if ((offset of "SSD Lifetime Left Indicator" in thisDriveDxReportSectionLine) is equal to 1) then set thisSSDLifetimeLeftIndicator to (text 40 thru -1 of thisDriveDxReportSectionLine)
38 | if ((offset of "Issues found" in thisDriveDxReportSectionLine) is equal to 1) then set thisIssuesFound to (text 40 thru -1 of thisDriveDxReportSectionLine)
39 | if ((offset of "Serial Number" in thisDriveDxReportSectionLine) is equal to 1) then
40 | set thisSerialNumber to (text 40 thru -1 of thisDriveDxReportSectionLine)
41 | exit repeat -- Serial Number is the last row we care about
42 | end if
43 | end if
44 | end repeat
45 | if ((thisSerialNumber is not equal to "UNKNOWN") and (thisAdvancedSmartStatus is not equal to "UNKNOWN") and (thisOverallHealthRating is not equal to "UNKNOWN") and (thisIssuesFound is not equal to "UNKNOWN")) then
46 | log "thisSerialNumber: " & thisSerialNumber
47 | log "thisAdvancedSmartStatus: " & thisAdvancedSmartStatus
48 | log "thisOverallHealthRating: " & thisOverallHealthRating
49 | if (thisOverallPerformanceRating is not equal to "UNKNOWN") then log "thisOverallPerformanceRating: " & thisOverallPerformanceRating
50 | if (thisSSDLifetimeLeftIndicator is not equal to "UNKNOWN") then log "thisSSDLifetimeLeftIndicator: " & thisSSDLifetimeLeftIndicator
51 | log "thisIssuesFound: " & thisIssuesFound
52 | log "-----"
53 | end if
54 | end repeat
--------------------------------------------------------------------------------
/Testing Scripts/Keyboard Test/Source/build_keyboard_test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # shellcheck enable=add-default-case,avoid-nullary-conditions,check-unassigned-uppercase,deprecate-which,quote-safe-variables,require-double-brackets
3 |
4 | # By: Pico Mitchell
5 | # For: MacLand @ Free Geek
6 | # Last Updated: 02/16/23
7 | #
8 | # MIT License
9 | #
10 | # Copyright (c) 2021 Free Geek
11 | #
12 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"),
13 | # to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
14 | # and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
15 | #
16 | # The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
17 | #
18 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20 | # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 | #
22 |
23 | # This script required Nativefier (https://github.com/jiahaog/nativefier) which is a Node.js package.
24 | # First, install Node.js: https://nodejs.org/en/download/
25 | # Then, install Nativifier by running the following command in a Terminal window: sudo npm install -g nativefier
26 | # Finally, run this script by drag-and-dropping it into a Terminal window.
27 |
28 | PATH='/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin' # PATH must include "/usr/local/bin" for npm (and node) and nativefier.
29 |
30 | PROJECT_PATH="$(cd "${BASH_SOURCE[0]%/*}" &> /dev/null && pwd -P)"
31 | readonly PROJECT_PATH
32 | readonly BUILD_DIR="${PROJECT_PATH}/.."
33 | readonly ZIPS_FOR_AUTO_UPDATE_PATH="${BUILD_DIR}/../../ZIPs for Auto-Update"
34 | readonly fgMIB_USERAPPS_PATH="${BUILD_DIR}/../../fgMIB Resources/Prepare OS Package/Package Resources/User/fg-demo/Apps/darwin-all-versions"
35 |
36 | readonly KEYBOARD_TESTER_URL='https://www.keyboardtester.com/tester.html'
37 |
38 | readonly APP_BUILD='1' # REMEMBER TO RESET THIS TO "1" IF CHANGED FROM PREVIOUS BUILD!
39 | APP_VERSION="$(date '+%Y.%-m.%-d')-${APP_BUILD}" # https://strftime.org
40 | readonly APP_VERSION
41 |
42 |
43 | echo -e '\nUPDATING NATIVEFIER (ADMIN PASSWORD REQUIRED)...'
44 | sudo npm install -g nativefier
45 |
46 |
47 | echo -e "\n\nBUILDING KEYBOARD TEST APP (VERSION ${APP_VERSION}) WITH NATIVEFIER..."
48 |
49 | rm -rf "${BUILD_DIR}/Keyboard Test-darwin-universal" "${BUILD_DIR}/Keyboard Test-darwin-x64" "${BUILD_DIR}/Keyboard Test.app" "${BUILD_DIR}/Keyboard-Test.zip" "${HOME}/Library/Application Support/keyboard-test"*
50 | # Any existing Application Support files are also being deleted since that stores the last window size which we don't want to remember if it's getting changed.
51 |
52 | nativefier \
53 | "${KEYBOARD_TESTER_URL}" \
54 | "${BUILD_DIR}" \
55 | --name 'Keyboard Test' \
56 | --arch 'universal' \
57 | --app-version "${APP_VERSION}" \
58 | --app-copyright '© KeyboardTester.com
59 |
60 | Modifications by Pico Mitchell for Free Geek
61 |
62 | App Wrapper Built with Nativefier
63 |
64 | App Icon is “Keyboard” from Twemoji by Twitter licensed under CC-BY 4.0' \
65 | --icon "${PROJECT_PATH}/Keyboard Test Icon/Twemoji Keyboard.icns" \
66 | --internal-urls "${KEYBOARD_TESTER_URL//./\\.}" \
67 | --strict-internal-urls \
68 | --inject "${PROJECT_PATH}/keyboard_test_modifications.js" \
69 | --inject "${PROJECT_PATH}/keyboard_test_modifications.css" \
70 | --min-width 939 \
71 | --min-height 490 \
72 | --max-width 939 \
73 | --max-height 490 \
74 | --disable-dev-tools \
75 | --disable-gpu \
76 | --disable-context-menu \
77 | --darwin-dark-mode-support \
78 | --fast-quit \
79 | --single-instance \
80 | --disable-old-build-warning-yesiknowitisinsecure # Do not ever want the Keyboard Test app to prompt "Old build detected" since it really doesn't matter for this kind of app.
81 |
82 | echo -e '\n\nMODIFYING KEYBOARD TEST APP Info.plist & MOVING INTO BUILD DIR...'
83 |
84 | app_info_plist_path="${BUILD_DIR}/Keyboard Test-darwin-universal/Keyboard Test.app/Contents/Info.plist"
85 |
86 | plutil -remove 'CFBundleVersion' "${app_info_plist_path}"
87 |
88 | plutil -replace 'LSMinimumSystemVersion' -string '10.13' "${app_info_plist_path}"
89 |
90 | plutil -replace 'LSMultipleInstancesProhibited' -bool 'true' "${app_info_plist_path}"
91 |
92 | mv "${BUILD_DIR}/Keyboard Test-darwin-universal/Keyboard Test.app/Contents/Resources/electron.icns" "${BUILD_DIR}/Keyboard Test-darwin-universal/Keyboard Test.app/Contents/Resources/Keyboard Test.icns"
93 | plutil -replace 'CFBundleIconFile' -string 'Keyboard Test' "${app_info_plist_path}"
94 |
95 | plutil -replace 'CFBundleIdentifier' -string 'org.freegeek.Keyboard-Test' "${app_info_plist_path}"
96 |
97 | rm -rf "${BUILD_DIR}/Keyboard Test.app"
98 | mv -f "${BUILD_DIR}/Keyboard Test-darwin-universal/Keyboard Test.app" "${BUILD_DIR}/Keyboard Test.app"
99 | rm -rf "${BUILD_DIR}/Keyboard Test-darwin-universal"
100 |
101 |
102 | echo -e '\n\nCODE SIGNING KEYBOARD TEST APP...'
103 | codesign -fs 'Developer ID Application' --deep --strict "${BUILD_DIR}/Keyboard Test.app"
104 |
105 |
106 | echo -e '\n\nZIPPING KEYBOARD TEST APP & UPDATING VERSION IN latest-versions.txt...'
107 |
108 | rm -f "${BUILD_DIR}/Keyboard-Test.zip"
109 | rm -f "${ZIPS_FOR_AUTO_UPDATE_PATH}/Keyboard-Test.zip"
110 | ditto -ck --keepParent --sequesterRsrc --zlibCompressionLevel 9 "${BUILD_DIR}/Keyboard Test.app" "${ZIPS_FOR_AUTO_UPDATE_PATH}/Keyboard-Test.zip"
111 |
112 | mkdir -p "${fgMIB_USERAPPS_PATH}"
113 | rm -f "${fgMIB_USERAPPS_PATH}/Keyboard-Test.zip"
114 | ditto "${ZIPS_FOR_AUTO_UPDATE_PATH}/Keyboard-Test.zip" "${fgMIB_USERAPPS_PATH}/Keyboard-Test.zip"
115 |
116 | if grep -qF 'Keyboard Test:' "${ZIPS_FOR_AUTO_UPDATE_PATH}/latest-versions.txt"; then
117 | sed -i '' "s/Keyboard Test: .*/Keyboard Test: ${APP_VERSION}/" "${ZIPS_FOR_AUTO_UPDATE_PATH}/latest-versions.txt"
118 | else
119 | echo "Keyboard Test: ${APP_VERSION}" >> "${ZIPS_FOR_AUTO_UPDATE_PATH}/latest-versions.txt"
120 | fi
121 |
122 | echo -e '\n\nDONE!\n'
123 |
--------------------------------------------------------------------------------
/Testing Scripts/Keyboard Test/Source/keyboard_test_modifications.css:
--------------------------------------------------------------------------------
1 | /*
2 | * By: Pico Mitchell
3 | * For: MacLand @ Free Geek
4 | * Last Updated: September 8th, 2022
5 | *
6 | * MIT License
7 | *
8 | * Copyright (c) 2021 Free Geek
9 | *
10 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"),
11 | * to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
12 | * and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
13 | *
14 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
18 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
19 | *
20 | */
21 |
22 | body {
23 | background-image: none !important;
24 | background-color: white;
25 | overflow: hidden;
26 | display: none;
27 | }
28 |
29 | #logo {
30 | margin: 10px 0px 6px 0px;
31 | text-align: center;
32 | }
33 |
34 | #logo a, #logo a:hover, #logo a:visited {
35 | text-decoration: none;
36 | color:#0d2693;
37 | font-size: 23px;
38 | }
39 |
40 | #resetbutton {
41 | padding: 0px !important;
42 | }
43 |
44 | #testarea {
45 | font-size: 16px;
46 | font-family: Arial, sans-serif;
47 | resize: none;
48 | display: block;
49 | margin: 0px auto 11px auto;
50 | padding: 5px;
51 | width: 877px;
52 | height: 123px;
53 | }
54 |
55 | #testarea::placeholder {
56 | text-align: center;
57 | font-weight: bold;
58 | font-size: 15px;
59 | }
60 |
61 | #hidden_text_before {
62 | width: 1px;
63 | height: 1px;
64 | border: 0px;
65 | }
66 |
67 | #soundArea, input[name="reset"], #key44, #key145, #key19, #key45, #key36, #key33, #key46, #key35, #key34, #key144, #key111, #key106, #key109b, #key103, #key104, #key105, #key107b, #key100, #key101, #key102, #key97, #key98, #key99, #key13b, #key96, #key110, #key224, #key224b, #key92, #key17b, .label, #notes {
68 | display: none;
69 | }
70 |
71 | #keyboard {
72 | padding-bottom: 100px; /* Push Ad Banner Down Below Window Height */
73 | background-color: white;
74 | }
75 |
76 | table {
77 | margin: 0px auto;
78 | }
79 |
80 | td {
81 | vertical-align: bottom !important;
82 | padding: 0px !important;
83 | }
84 |
85 | .key_block td, .key_row div { /* All Keys */
86 | font-size: 16px;
87 | font-family: Arial, sans-serif !important;
88 | border-radius: 6px;
89 | padding: 6px 8px !important;
90 | min-width: 22px;
91 | text-align: center;
92 | background-color: white;
93 | }
94 |
95 | .key_highlight {
96 | background-color: #FFC864 !important;
97 | }
98 |
99 | .key_pressed, .key_pressed_m {
100 | border-color: green !important;
101 | background-color: #2CB32C !important;
102 | color: white;
103 | }
104 |
105 | .key_small div { /* Function Keys */
106 | font-size: 12px;
107 | min-width: 29px;
108 | }
109 |
110 | #key8, #key9 { /* Delete, Tab */
111 | min-width: 63px;
112 | }
113 |
114 | #key20 { /* Caps */
115 | min-width: 68px;
116 | }
117 |
118 | #key13 { /* Return */
119 | min-width: 67px;
120 | }
121 |
122 | #key16 { /* Left Shift */
123 | min-width: 92px;
124 | }
125 |
126 | #key16b { /* Right Shift */
127 | min-width: 93px;
128 | }
129 |
130 | #key32 { /* Spacebar */
131 | min-width: 297px;
132 | }
133 |
134 | table.key_block { /* Arrow Keys */
135 | margin: 0px -4px -4px 0px;
136 | }
137 |
138 | .key_block td { /* Arrow Keys */
139 | font-size: 11px;
140 | vertical-align: middle !important;
141 | }
142 |
143 | #key38, #key40 { /* Up Arrow, Down Arrow */
144 | font-size: 12px;
145 | padding: 8px !important;
146 | }
147 |
148 | @media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) {
149 | #testarea {
150 | padding: 10px;
151 | width: 867px;
152 | height: 105px;
153 | }
154 |
155 | #key32 { /* Spacebar */
156 | min-width: 297.5px;
157 | }
158 |
159 | #key38, #key40 { /* Up Arrow, Down Arrow */
160 | font-size: 12.5px;
161 | }
162 | }
--------------------------------------------------------------------------------
/Testing Scripts/Keyboard Test/Source/keyboard_test_modifications.js:
--------------------------------------------------------------------------------
1 | /*
2 | * By: Pico Mitchell
3 | * For: MacLand @ Free Geek
4 | * Last Updated: September 8th, 2022
5 | *
6 | * MIT License
7 | *
8 | * Copyright (c) 2021 Free Geek
9 | *
10 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"),
11 | * to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
12 | * and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
13 | *
14 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
18 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
19 | *
20 | */
21 |
22 | try {
23 | document.title = 'Keyboard Test'
24 |
25 | window.addEventListener('scroll', function() { window.scrollTo(0, 0) })
26 |
27 | const resetButton = document.getElementById('resetbutton')
28 | resetButton.outerHTML = '