diff --git a/Jamf Pro Custom Schema/Jamf Pro Custom Schema.json b/Jamf Pro Custom Schema/Jamf Pro Custom Schema.json index 73c1cd7..2c124f0 100644 --- a/Jamf Pro Custom Schema/Jamf Pro Custom Schema.json +++ b/Jamf Pro Custom Schema/Jamf Pro Custom Schema.json @@ -105,21 +105,6 @@ } ] }, - "HideMajorUpdates": { - "title": "Hide major macOS updates", - "description": "Ignore macOS major updates. This will prevent the menu bar icon and the macOS version info item from showing an available major update. Only applicable to macOS 12.3 and higher", - "type": "boolean", - "options": { - "enum_titles": ["Hide", "Show"], - "infoText": "Key name: HideMajorUpdates" - }, - "links": [ - { - "rel": "Documentation", - "href": "https://github.com/root3nl/SupportApp" - } - ] - }, "UpdateText": { "title": "Update Text", "description": "Optional text to shown in the macOS Managed Updates popover to tell users about the updates", diff --git a/README.md b/README.md index 41837bc..1a4b17a 100644 --- a/README.md +++ b/README.md @@ -199,6 +199,9 @@ The popover allows the user to open System Settings and install the update or up +> **Note** +> When a deferral is set using the preference key `forceDelayedMajorSoftwareUpdates` in the domain `com.apple.applicationaccess`, major macOS updates will automatically be hidden indefinitely until the key is removed or set to `false`. The amount of days configured for the deferral are ignored. Due to limitations and complexity, it is not supported to automatically show the macOS major update once the deferral days are passed. This behaviour replaces the `HideMajorUpdates` key, previously available in version 2.5 and earlier. More info here: https://developer.apple.com/documentation/devicemanagement/restrictions + ## Configuration The configuration of the Support app is optimized for use with your MDM solution. The easiest way to configure the app is using a Configuration Profile so you can use whatever MDM solution you like, as long as it supports custom Configuration Profiles. @@ -219,7 +222,6 @@ All general settings | StatusBarIcon | String | Root3 Logo | Remote URL or path to the status bar icon shown in the menu bar. Recommended: PNG, 16x16 points. Icons larger than 22 points will automatically be resized to 16 points. A subfolder in `/Library/Application Support/` is the recommended location due to sandboxing | `/Library/Application Support/Your Company/statusbaricon.png` or `https://domain.tld/url_to_image.png` | | StatusBarIconSFSymbol | String | Root3 Logo | Custom status bar icon using an SF Symbol. Ignored when StatusBarIcon is also set | “lifepreserver” | | StatusBarIconNotifierEnabled | Boolean | false | Shows a small notification badge in the Status Bar Icon when an info items triggers a warning or notification | true | -| HideMajorUpdates | Boolean | false | Ignore macOS major updates. This will prevent the menu bar icon and the macOS version info item from showing an available major update. Only applicable to macOS 12.3 and higher | true | | UpdateText | String | - | The text shown below the software update details popover | "Your organization requires you to update as soon as possible. [More info...](https://URL_TO_YOUR_UPDATE_POLICY)" | | CustomColor | String | macOS Accent Color | Custom color for all symbols. Leave empty to use macOS Accent Color. We recommend not to use a very light color as text may become hard to read | HEX color in RGB format like "#8cc63f" | | CustomColorDarkMode | String | macOS Accent Color | Custom color for all symbols in Dark Mode. Leave empty to use macOS Accent Color or CustomColor if specified. We recommend not to use a very dark color as text may become hard to read | HEX color in RGB format like "#8cc63f" | diff --git a/pkgbuild/scripts/postinstall b/pkgbuild/scripts/postinstall index 32b0f17..c314a8f 100755 --- a/pkgbuild/scripts/postinstall +++ b/pkgbuild/scripts/postinstall @@ -26,7 +26,7 @@ install_location="/Applications/Support.app" username=$(scutil <<< "show State:/Users/ConsoleUser" | awk '/Name :/ && ! /loginwindow/ { print $3 }') # Remove "Downloaded from Internet" warning -xattr -d -r com.apple.quarantine "${install_location}" +# xattr -d -r com.apple.quarantine "${install_location}" # Load Requirements autoload is-at-least @@ -34,6 +34,16 @@ autoload is-at-least # macOS Version os_version=$(sw_vers -productVersion) +# ------------------ Gatekeeper scan ------------------ + +# Perform a Gatekeeper scan. This is useful for pre-warming the cache so users +# do not see the 'Verifying...' dialog on first launch of an application. +if is-at-least 14.0 ${os_version}; then + gktool scan "${install_location}" +fi + +# ------------------ LaunchAgent ------------------ + # Open the app so the legacy LaunchAgent properly displays the app name and icon # in System Settings > General > Login Items # Tested and does only work when a user is logged in during the installation diff --git a/src/Support.xcodeproj/project.pbxproj b/src/Support.xcodeproj/project.pbxproj index a7dfa41..0334daa 100644 --- a/src/Support.xcodeproj/project.pbxproj +++ b/src/Support.xcodeproj/project.pbxproj @@ -569,7 +569,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 11.0; - MARKETING_VERSION = 2.5; + MARKETING_VERSION = 2.5.1; PRODUCT_BUNDLE_IDENTIFIER = nl.root3.support; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -598,7 +598,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 11.0; - MARKETING_VERSION = 2.5; + MARKETING_VERSION = 2.5.1; PRODUCT_BUNDLE_IDENTIFIER = nl.root3.support; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; diff --git a/src/Support/AppDelegate.swift b/src/Support/AppDelegate.swift index 5ed0329..1f233b6 100644 --- a/src/Support/AppDelegate.swift +++ b/src/Support/AppDelegate.swift @@ -34,6 +34,9 @@ class AppDelegate: NSObject, NSApplicationDelegate { // Make UserDefaults easy to use with suite "com.apple.SoftwareUpdate" let ASUdefaults = UserDefaults(suiteName: "com.apple.SoftwareUpdate") + // Make UserDefaults easy to use with suite "com.apple.applicationaccess" + let restrictionsDefaults = UserDefaults(suiteName: "com.apple.applicationaccess") + // Make properties and preferences available var computerinfo = ComputerInfo() var userinfo = UserInfo() @@ -108,6 +111,7 @@ class AppDelegate: NSObject, NSApplicationDelegate { defaults.addObserver(self, forKeyPath: "StorageLimit", options: .new, context: nil) defaults.addObserver(self, forKeyPath: "PasswordExpiryLimit", options: .new, context: nil) defaults.addObserver(self, forKeyPath: "OpenAtLogin", options: .new, context: nil) + restrictionsDefaults?.addObserver(self, forKeyPath: "forceDelayedMajorSoftwareUpdates", options: .new, context: nil) ASUdefaults?.addObserver(self, forKeyPath: "LastUpdatesAvailable", options: .new, context: nil) ASUdefaults?.addObserver(self, forKeyPath: "RecommendedUpdates", options: .new, context: nil) @@ -289,22 +293,18 @@ class AppDelegate: NSObject, NSApplicationDelegate { preferences.infoItemSix ] - // Number of available software updates - var updatesAvailable = computerinfo.updatesAvailable - // If configured, ignore major macOS version updates - if preferences.hideMajorUpdates { - logger.debug("HideMajorUpdates is enabled, hiding \(self.computerinfo.majorVersionUpdates) major macOS updates") - updatesAvailable -= computerinfo.majorVersionUpdates + if computerinfo.forceDelayedMajorSoftwareUpdates { + logger.debug("forceDelayedMajorSoftwareUpdates is enabled, hiding \(self.computerinfo.majorVersionUpdates) major macOS updates") } // Show notification badge in menu bar icon when info item when needed - if (updatesAvailable == 0 || !infoItemsEnabled.contains("MacOSVersion")) && ((computerinfo.uptimeLimitReached && infoItemsEnabled.contains("Uptime")) || (computerinfo.selfSignedIP && infoItemsEnabled.contains("Network")) || (userinfo.passwordExpiryLimitReached && infoItemsEnabled.contains("Password")) || (computerinfo.storageLimitReached && infoItemsEnabled.contains("Storage")) || (preferences.extensionAlertA && infoItemsEnabled.contains("ExtensionA")) || (preferences.extensionAlertB && infoItemsEnabled.contains("ExtensionB"))) && defaults.bool(forKey: "StatusBarIconNotifierEnabled") { + if (computerinfo.updatesAvailableToShow == 0 || !infoItemsEnabled.contains("MacOSVersion")) && ((computerinfo.uptimeLimitReached && infoItemsEnabled.contains("Uptime")) || (computerinfo.selfSignedIP && infoItemsEnabled.contains("Network")) || (userinfo.passwordExpiryLimitReached && infoItemsEnabled.contains("Password")) || (computerinfo.storageLimitReached && infoItemsEnabled.contains("Storage")) || (preferences.extensionAlertA && infoItemsEnabled.contains("ExtensionA")) || (preferences.extensionAlertB && infoItemsEnabled.contains("ExtensionB"))) && defaults.bool(forKey: "StatusBarIconNotifierEnabled") { // Create orange notification badge orangeBadge.isHidden = false - } else if (updatesAvailable > 0 && infoItemsEnabled.contains("MacOSVersion")) && defaults.bool(forKey: "StatusBarIconNotifierEnabled") { + } else if (computerinfo.updatesAvailableToShow > 0 && infoItemsEnabled.contains("MacOSVersion")) && defaults.bool(forKey: "StatusBarIconNotifierEnabled") { // Create red notification badge redBadge.isHidden = false @@ -408,6 +408,9 @@ class AppDelegate: NSObject, NSApplicationDelegate { case "OpenAtLogin": logger.debug("\(keyPath! as NSObject) changed to \(self.defaults.bool(forKey: "OpenAtLogin"), privacy: .public)") self.configureLaunchAgent() + case "forceDelayedMajorSoftwareUpdates": + logger.debug("\(keyPath! as NSObject) changed to \(self.restrictionsDefaults!.bool(forKey: "forceDelayedMajorSoftwareUpdates"), privacy: .public)") + self.computerinfo.getRecommendedUpdates() case "ExtensionAlertA": logger.debug("\(keyPath! as NSObject) changed to \(self.preferences.extensionAlertA, privacy: .public)") case "ExtensionAlertB": diff --git a/src/Support/ComputerInfo.swift b/src/Support/ComputerInfo.swift index 14a82ae..770d116 100644 --- a/src/Support/ComputerInfo.swift +++ b/src/Support/ComputerInfo.swift @@ -71,6 +71,18 @@ class ComputerInfo: ObservableObject { // Number of major macOS Software Updates @Published var majorVersionUpdates: Int = 0 + // Calculate number of updates to show, excluding any major upgrades if hidden using 'forceDelayedMajorSoftwareUpdates' in a restrictions profile + var updatesAvailableToShow: Int { + if forceDelayedMajorSoftwareUpdates { + return updatesAvailable - majorVersionUpdates + } else { + return updatesAvailable + } + } + + // Get if major OS updates are deferred using a restrictions profile + @AppStorage("forceDelayedMajorSoftwareUpdates", store: UserDefaults(suiteName: "com.apple.applicationaccess")) var forceDelayedMajorSoftwareUpdates: Bool = false + // Computer name @Published var hostname = String() @@ -325,7 +337,7 @@ class ComputerInfo: ObservableObject { // Command to get c let computerNameCommand = """ - ioreg -l -c IOPlatformDevice | grep -e "product-name" | cut -d'"' -f 4 + ioreg -c IOPlatformDevice | grep -e "product-name" | cut -d'"' -f 4 """ // Move command to background thread @@ -652,6 +664,12 @@ class ComputerInfo: ObservableObject { // Return when decoded RecommendedUpdates array is empty guard !decodedItems.isEmpty else { self.logger.debug("RecommendedUpdates is empty...") + + // Remove all updates from UI + DispatchQueue.main.async { + self.recommendedUpdates = [] + } + return } @@ -661,7 +679,7 @@ class ComputerInfo: ObservableObject { self.logger.debug("Updates found: \(decodedItems.count)") // Loop through all available updates and decrease number of updates when available macOS version is higher than current major version - for item in decodedItems { + for (index, item) in decodedItems.enumerated() { // Filter updates with "macOS" in Display Name if item.displayName.contains("macOS") { // Get digits from Display Version separated by a dot to get the major version @@ -669,8 +687,12 @@ class ComputerInfo: ObservableObject { self.logger.debug("macOS update found: \(item.displayName, privacy: .public)") // Convert to integer and compare with current major OS version. If higher, increase number of major OS updates if Int(version) ?? 0 > self.systemVersionMajor { - self.logger.debug("macOS version \(version, privacy: .public) is higher than the current macOS version (\(self.systemVersionMajor)), update will be hidden when DeferMajorVersions is enabled") + self.logger.debug("macOS version \(version, privacy: .public) is higher than the current macOS version (\(self.systemVersionMajor)), update will be hidden when forceDelayedMajorSoftwareUpdates is enabled") majorVersionUpdatesTemp += 1 + // Remove update item from array if forceDelayedMajorSoftwareUpdates is enabled + if self.forceDelayedMajorSoftwareUpdates && decodedItems.indices.contains(index) { + decodedItems.remove(at: index) + } } } else { self.logger.error("Error getting macOS version from \(item.displayName, privacy: .public)") diff --git a/src/Support/Extensions.swift b/src/Support/Extensions.swift index 9b9b6c9..ef4cc0c 100644 --- a/src/Support/Extensions.swift +++ b/src/Support/Extensions.swift @@ -161,7 +161,7 @@ extension String { ("$LocalMacosVersionName", computerInfo.macOSVersionName), ("$LocalSerialNumber", computerInfo.deviceSerialNumber), ("$LocalIpAddress", computerInfo.ipAddress), - ("$LocalUpdatesAvailable", "\(computerInfo.updatesAvailable)"), + ("$LocalUpdatesAvailable", "\(computerInfo.updatesAvailableToShow)"), ("\\n", "\n") ] diff --git a/src/Support/Info.plist b/src/Support/Info.plist index b826edd..a1c96ea 100644 --- a/src/Support/Info.plist +++ b/src/Support/Info.plist @@ -19,7 +19,7 @@ CFBundleShortVersionString $(MARKETING_VERSION) CFBundleVersion - 1690815745 + 1695998775 LSApplicationCategoryType public.app-category.utilities LSMinimumSystemVersion diff --git a/src/Support/Preferences.swift b/src/Support/Preferences.swift index c0eba0e..5135e79 100644 --- a/src/Support/Preferences.swift +++ b/src/Support/Preferences.swift @@ -41,9 +41,6 @@ class Preferences: ObservableObject { // Automatically register modern LaunchAgent on macOS 13 and higher @AppStorage("OpenAtLogin") var openAtLogin: Bool = false - // Hide major updates for macOS - @AppStorage("HideMajorUpdates") var hideMajorUpdates: Bool = false - // Optional text to show in the Managed Updates view @AppStorage("UpdateText") var updateText: String = "" diff --git a/src/Support/Support.entitlements b/src/Support/Support.entitlements index d48714e..b948868 100644 --- a/src/Support/Support.entitlements +++ b/src/Support/Support.entitlements @@ -8,6 +8,7 @@ com.apple.security.temporary-exception.shared-preference.read-only + com.apple.applicationaccess com.trusourcelabs.NoMAD com.apple.SoftwareUpdate com.jamf.connect.state diff --git a/src/Support/Views/ButtonViews/MacOSVersionSubview.swift b/src/Support/Views/ButtonViews/MacOSVersionSubview.swift index ac69c42..a696587 100644 --- a/src/Support/Views/ButtonViews/MacOSVersionSubview.swift +++ b/src/Support/Views/ButtonViews/MacOSVersionSubview.swift @@ -35,23 +35,14 @@ struct MacOSVersionSubview: View { } } - // Calculate number of updates to show - var updatesAvailable: Int { - if preferences.hideMajorUpdates { - return computerinfo.updatesAvailable - computerinfo.majorVersionUpdates - } else { - return computerinfo.updatesAvailable - } - } - var body: some View { - InfoItem(title: "macOS \(computerinfo.macOSVersionName)", subtitle: computerinfo.macOSVersion, image: "applelogo", symbolColor: Color(NSColor(hex: "\(customColor)") ?? NSColor.controlAccentColor), notificationBadge: updatesAvailable, hoverEffectEnable: true) + InfoItem(title: "macOS \(computerinfo.macOSVersionName)", subtitle: computerinfo.macOSVersion, image: "applelogo", symbolColor: Color(NSColor(hex: "\(customColor)") ?? NSColor.controlAccentColor), notificationBadge: computerinfo.updatesAvailableToShow, hoverEffectEnable: true) .onTapGesture { self.showUpdatePopover.toggle() } .popover(isPresented: $showUpdatePopover, arrowEdge: .leading) { - UpdateView(updateCounter: updatesAvailable, color: Color(NSColor(hex: "\(customColor)") ?? NSColor.controlAccentColor)) + UpdateView(updateCounter: computerinfo.updatesAvailableToShow, color: Color(NSColor(hex: "\(customColor)") ?? NSColor.controlAccentColor)) } } } diff --git a/src/Support/Views/UpdateView.swift b/src/Support/Views/UpdateView.swift index 03a0243..3801cc5 100644 --- a/src/Support/Views/UpdateView.swift +++ b/src/Support/Views/UpdateView.swift @@ -32,7 +32,7 @@ struct UpdateView: View { VStack(alignment: .leading, spacing: 8) { - if updateCounter > 0 { + if computerinfo.recommendedUpdates.count > 0 { HStack {