-
Notifications
You must be signed in to change notification settings - Fork 174
/
SoftwareUpdate.swift
115 lines (95 loc) · 5.02 KB
/
SoftwareUpdate.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
//
// SoftwareUpdate.swift
// Nudge
//
// Created by Rory Murdock on 2/10/21.
//
import Foundation
import os
class SoftwareUpdate {
func list() -> String {
let (output, error, exitCode) = runProcess(launchPath: "/usr/sbin/softwareupdate", arguments: ["--list", "--all"])
if exitCode != 0 {
LogManager.error("Error listing software updates: \(error)", logger: softwareupdateListLog)
return error
} else {
LogManager.info("\(output)", logger: softwareupdateListLog)
return output
}
}
func download() {
LogManager.notice("enforceMinorUpdates: \(OptionalFeatureVariables.enforceMinorUpdates)", logger: softwareupdateDownloadLog)
if DeviceManager().getCPUTypeString() == "Apple Silicon" && !AppStateManager().requireMajorUpgrade() {
LogManager.debug("Apple Silicon devices do not support automated softwareupdate downloads for minor updates. Please use MDM for this functionality.", logger: softwareupdateListLog)
return
}
if AppStateManager().requireMajorUpgrade() {
guard FeatureVariables.actionButtonPath == nil else { return }
if OptionalFeatureVariables.attemptToFetchMajorUpgrade, !majorUpgradeAppPathExists, !majorUpgradeBackupAppPathExists {
LogManager.notice("Device requires major upgrade - attempting download", logger: softwareupdateListLog)
let (output, error, exitCode) = runProcess(launchPath: "/usr/sbin/softwareupdate", arguments: ["--fetch-full-installer", "--full-installer-version", OSVersionRequirementVariables.requiredMinimumOSVersion])
if exitCode != 0 {
LogManager.error("Error downloading software update: \(error)", logger: softwareupdateDownloadLog)
} else {
LogManager.info("\(output)", logger: softwareupdateDownloadLog)
GlobalVariables.fetchMajorUpgradeSuccessful = true
// Update the state based on the download result
}
} else if majorUpgradeAppPathExists || majorUpgradeBackupAppPathExists {
LogManager.notice("Found major upgrade application or backup - skipping download", logger: softwareupdateListLog)
}
} else {
if OptionalFeatureVariables.disableSoftwareUpdateWorkflow {
LogManager.notice("Skipping running softwareupdate because it's disabled by a preference.", logger: softwareupdateListLog)
return
}
let softwareupdateList = self.list()
let updateLabel = extractUpdateLabel(from: softwareupdateList)
if !softwareupdateList.contains(OSVersionRequirementVariables.requiredMinimumOSVersion) || updateLabel.isEmpty {
LogManager.notice("Software update did not find \(OSVersionRequirementVariables.requiredMinimumOSVersion) available for download - skipping download attempt", logger: softwareupdateListLog)
return
}
LogManager.notice("Software update found \(updateLabel) available for download - attempting download", logger: softwareupdateListLog)
let (output, error, exitCode) = runProcess(launchPath: "/usr/sbin/softwareupdate", arguments: ["--download", updateLabel])
if exitCode != 0 {
LogManager.error("Error downloading software updates: \(error)", logger: softwareupdateDownloadLog)
} else {
LogManager.info("\(output)", logger: softwareupdateDownloadLog)
}
}
}
private func extractUpdateLabel(from softwareupdateList: String) -> String {
let lines = softwareupdateList.split(separator: "\n")
var updateLabel: String?
for line in lines {
if line.contains("Label:") {
let labelPart = line.split(separator: ":").map { $0.trimmingCharacters(in: .whitespaces) }
if labelPart.count > 1 && labelPart[1].contains(OSVersionRequirementVariables.requiredMinimumOSVersion) {
updateLabel = labelPart[1]
break
}
}
}
return updateLabel ?? ""
}
private func runProcess(launchPath: String, arguments: [String]) -> (output: String, error: String, exitCode: Int32) {
let task = Process()
task.launchPath = launchPath
task.arguments = arguments
let outputPipe = Pipe()
let errorPipe = Pipe()
task.standardOutput = outputPipe
task.standardError = errorPipe
do {
try task.run()
} catch {
return ("", "Error running process", -1)
}
task.waitUntilExit()
let outputData = outputPipe.fileHandleForReading.readDataToEndOfFile()
let errorData = errorPipe.fileHandleForReading.readDataToEndOfFile()
let output = String(decoding: outputData, as: UTF8.self)
let error = String(decoding: errorData, as: UTF8.self)
return (output, error, task.terminationStatus)
}
}