Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for unattended windows installs #6438

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Configuration/UTMQemuConfigurationQEMU.swift
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ struct UTMQemuConfigurationQEMU: Codable {
/// Set to true to request guest tools install. Not saved.
var isGuestToolsInstallRequested: Bool = false

var unattended: Bool = false

/// Set to true to request UEFI variable reset. Not saved.
var isUefiVariableResetRequested: Bool = false

Expand Down
3 changes: 2 additions & 1 deletion Platform/Shared/VMContextMenuModifier.swift
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,8 @@ struct VMContextMenuModifier: ViewModifier {
.onChange(of: (vm.config as? UTMQemuConfiguration)?.qemu.isGuestToolsInstallRequested) { newValue in
if newValue == true {
data.busyWorkAsync {
try await data.mountSupportTools(for: vm.wrapped!)
let unattend = await (vm.config as? UTMQemuConfiguration)?.qemu.unattended ?? false
try await data.mountSupportTools(for: vm.wrapped!, unattendless: unattend)
}
}
}
Expand Down
1 change: 1 addition & 0 deletions Platform/Shared/VMWizardOSWindowsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ struct VMWizardOSWindowsView: View {
} label: {
Label("Fetch latest Windows installer…", systemImage: "link")
}.buttonStyle(.link)
Toggle("Unattended Installation", isOn: $wizardState.windowsUnattendedInstall)
}
#endif
Link(destination: URL(string: "https://docs.getutm.app/guides/windows/")!) {
Expand Down
661 changes: 658 additions & 3 deletions Platform/Shared/VMWizardState.swift

Large diffs are not rendered by default.

48 changes: 48 additions & 0 deletions Platform/Shared/VMWizardWindowsUnattendView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
//
// Copyright © 2021 osy. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

import SwiftUI

struct VMWizardWindowsUnattendView: View {
@ObservedObject var wizardState: VMWizardState
var body: some View {
VMWizardContent("Unattended Installation") {
Section {
Form {
TextField("Language", text: $wizardState.unattendLanguage)
.keyboardType(.asciiCapable)
.lineLimit(1)
TextField("Username", text: $wizardState.unattendUsername)
.keyboardType(.asciiCapable)
.lineLimit(1)
SecureField("Password", text: $wizardState.unattendPassword)
.keyboardType(.asciiCapable)
.lineLimit(1)
}
} header: {
Text("Configuration")
}
}
}
}

struct VMWizardWindowsUnattendView_Previews: PreviewProvider {
@StateObject static var wizardState = VMWizardState()

static var previews: some View {
VMWizardWindowsUnattendView(wizardState: wizardState)
}
}
4 changes: 2 additions & 2 deletions Platform/UTMData.swift
Original file line number Diff line number Diff line change
Expand Up @@ -737,11 +737,11 @@ struct AlertMessage: Identifiable {
}
}

func mountSupportTools(for vm: any UTMVirtualMachine) async throws {
func mountSupportTools(for vm: any UTMVirtualMachine, unattendless: Bool) async throws {
guard let vm = vm as? any UTMSpiceVirtualMachine else {
throw UTMDataError.unsupportedBackend
}
let task = UTMDownloadSupportToolsTask(for: vm)
let task = UTMDownloadSupportToolsTask(for: vm, unattendless: unattendless)
if await task.hasExistingSupportTools {
vm.config.qemu.isGuestToolsInstallRequested = false
_ = try await task.mountTools()
Expand Down
17 changes: 14 additions & 3 deletions Platform/UTMDownloadSupportToolsTask.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,21 @@ import Foundation
/// Downloads support tools ISO
class UTMDownloadSupportToolsTask: UTMDownloadTask {
private let vm: any UTMSpiceVirtualMachine
private let unattend: Bool

private static let supportToolsDownloadUrl = URL(string: "https://getutm.app/downloads/utm-guest-tools-latest.iso")!
private static let supportToolsDownloadUrlUa = URL(string: "https://getutm.app/downloads/unattendless.iso")!

private var toolsUrl: URL {
fileManager.urls(for: .applicationSupportDirectory, in: .userDomainMask).first!.appendingPathComponent("GuestSupportTools")
}

private var supportToolsLocalUrl: URL {
toolsUrl.appendingPathComponent(Self.supportToolsDownloadUrl.lastPathComponent)
var url = Self.supportToolsDownloadUrl
if unattend {
url = Self.supportToolsDownloadUrlUa
}
return toolsUrl.appendingPathComponent(url.lastPathComponent)
}

@Setting("LastDownloadedGuestTools")
Expand All @@ -42,10 +48,15 @@ class UTMDownloadSupportToolsTask: UTMDownloadTask {
}
}

init(for vm: any UTMSpiceVirtualMachine) {
init(for vm: any UTMSpiceVirtualMachine, unattendless: Bool) {
self.vm = vm
let name = NSLocalizedString("Windows Guest Support Tools", comment: "UTMDownloadSupportToolsTask")
super.init(for: Self.supportToolsDownloadUrl, named: name)
self.unattend = unattendless
var url = Self.supportToolsDownloadUrl
if unattend {
url = Self.supportToolsDownloadUrlUa
}
super.init(for: url, named: name)
}

override func processCompletedDownload(at location: URL, response: URLResponse?) async throws -> any UTMVirtualMachine {
Expand Down
2 changes: 2 additions & 0 deletions Platform/iOS/VMWizardView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,8 @@ fileprivate struct WizardViewWrapper: View {
VMWizardOSView(wizardState: wizardState)
case .macOSBoot:
EmptyView()
case .windowsUnattendConfig:
VMWizardWindowsUnattendView(wizardState: wizardState)
case .linuxBoot:
VMWizardOSLinuxView(wizardState: wizardState)
case .windowsBoot:
Expand Down
4 changes: 4 additions & 0 deletions Platform/macOS/VMWizardView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ struct VMWizardView: View {
case .windowsBoot:
VMWizardOSWindowsView(wizardState: wizardState)
.transition(wizardState.slide)
case .windowsUnattendConfig:
VMWizardWindowsUnattendView(wizardState: wizardState)
.transition(wizardState.slide)
case .hardware:
VMWizardHardwareView(wizardState: wizardState)
.transition(wizardState.slide)
Expand Down Expand Up @@ -115,6 +118,7 @@ struct VMWizardView: View {
_ = try await data.create(config: qemuConfig)
await MainActor.run {
qemuConfig.qemu.isGuestToolsInstallRequested = wizardState.isGuestToolsInstallRequested
qemuConfig.qemu.unattended = wizardState.windowsUnattendedInstall
}
} else if let appleConfig = config as? UTMAppleConfiguration {
_ = try await data.create(config: appleConfig)
Expand Down
2 changes: 1 addition & 1 deletion Remote/UTMRemoteServer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -812,7 +812,7 @@ extension UTMRemoteServer {
private func _mountGuestToolsOnVirtualMachine(parameters: M.MountGuestToolsOnVirtualMachine.Request) async throws -> M.MountGuestToolsOnVirtualMachine.Reply {
let vm = try await findVM(withId: parameters.id)
if let wrapped = await vm.wrapped {
try await data.mountSupportTools(for: wrapped)
try await data.mountSupportTools(for: wrapped, unattendless: false)
}
return .init()
}
Expand Down
10 changes: 10 additions & 0 deletions UTM.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
objects = {

/* Begin PBXBuildFile section */
003B61A42C03EC5B0013BA74 /* VMWizardWindowsUnattendView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 003B61A32C03EC5B0013BA74 /* VMWizardWindowsUnattendView.swift */; };
003B61A52C03EC5B0013BA74 /* VMWizardWindowsUnattendView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 003B61A32C03EC5B0013BA74 /* VMWizardWindowsUnattendView.swift */; };
003B61A62C03EC5B0013BA74 /* VMWizardWindowsUnattendView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 003B61A32C03EC5B0013BA74 /* VMWizardWindowsUnattendView.swift */; };
003B61A72C03EC5B0013BA74 /* VMWizardWindowsUnattendView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 003B61A32C03EC5B0013BA74 /* VMWizardWindowsUnattendView.swift */; };
2C33B3A92566C9B100A954A6 /* VMContextMenuModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C33B3A82566C9B100A954A6 /* VMContextMenuModifier.swift */; };
2C33B3AA2566C9B100A954A6 /* VMContextMenuModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C33B3A82566C9B100A954A6 /* VMContextMenuModifier.swift */; };
2C6D9E03256EE454003298E6 /* VMDisplayQemuTerminalWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C6D9E02256EE454003298E6 /* VMDisplayQemuTerminalWindowController.swift */; };
Expand Down Expand Up @@ -1584,6 +1588,7 @@
/* End PBXCopyFilesBuildPhase section */

/* Begin PBXFileReference section */
003B61A32C03EC5B0013BA74 /* VMWizardWindowsUnattendView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VMWizardWindowsUnattendView.swift; sourceTree = "<group>"; };
037DAA1C2B0B92580061ACB3 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/VMDisplayWindow.strings; sourceTree = "<group>"; };
037DAA1D2B0B92580061ACB3 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/InfoPlist.strings; sourceTree = "<group>"; };
037DAA1E2B0B92580061ACB3 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/InfoPlist.strings; sourceTree = "<group>"; };
Expand Down Expand Up @@ -2972,6 +2977,7 @@
83034C0626AB630F006B4BAF /* UTMPendingVMView.swift */,
84909A8C27CACD5C005605F1 /* UTMPlaceholderVMView.swift */,
84909A9027CADAE0005605F1 /* UTMUnavailableVMView.swift */,
003B61A32C03EC5B0013BA74 /* VMWizardWindowsUnattendView.swift */,
);
path = Shared;
sourceTree = "<group>";
Expand Down Expand Up @@ -3605,6 +3611,7 @@
CEF0307426A2B40B00667B63 /* VMWizardHardwareView.swift in Sources */,
841E997528AA1191003C6CB6 /* UTMRegistry.swift in Sources */,
8401868F288A50B90050AC51 /* VMDisplayViewControllerDelegate.swift in Sources */,
003B61A42C03EC5B0013BA74 /* VMWizardWindowsUnattendView.swift in Sources */,
CE2D92D724AD46670059923A /* UTMLogging.m in Sources */,
848D99A8285DB5550055C215 /* VMConfigConstantPicker.swift in Sources */,
8453DCB4278CE5410037A0DA /* UTMQemuImage.swift in Sources */,
Expand Down Expand Up @@ -3787,6 +3794,7 @@
CE0B6CF524AD568400FE012D /* UTMLegacyQemuConfiguration+Miscellaneous.m in Sources */,
CE25125129C806AF000790AB /* UTMScriptingDeleteCommand.swift in Sources */,
CE0B6CFB24AD568400FE012D /* UTMLegacyQemuConfiguration+Networking.m in Sources */,
003B61A72C03EC5B0013BA74 /* VMWizardWindowsUnattendView.swift in Sources */,
84C584E5268F8C65000FCABF /* VMAppleSettingsView.swift in Sources */,
CE9B15442B11A74E003A32DD /* UTMRemoteKeyManager.swift in Sources */,
84F746BB276FF70700A20C87 /* VMDisplayQemuDisplayController.swift in Sources */,
Expand Down Expand Up @@ -4021,6 +4029,7 @@
CEA45F00263519B5002FA97D /* VMCardView.swift in Sources */,
CEA45F01263519B5002FA97D /* UTMLegacyQemuConfiguration+Sharing.m in Sources */,
841619B328431DA5000034B2 /* UTMQemuConfigurationQEMU.swift in Sources */,
003B61A52C03EC5B0013BA74 /* VMWizardWindowsUnattendView.swift in Sources */,
CEF0305C26A2AFDF00667B63 /* VMWizardOSOtherView.swift in Sources */,
CEA45F08263519B5002FA97D /* ActivityView.swift in Sources */,
848F71E9277A2A4E006A0240 /* UTMSerialPort.swift in Sources */,
Expand Down Expand Up @@ -4138,6 +4147,7 @@
CEF7F5E82AEEDCC400E34952 /* VMDisplayViewControllerDelegate.swift in Sources */,
CEF7F5EA2AEEDCC400E34952 /* VMConfigConstantPicker.swift in Sources */,
CEF7F5EC2AEEDCC400E34952 /* VMToolbarModifier.swift in Sources */,
003B61A62C03EC5B0013BA74 /* VMWizardWindowsUnattendView.swift in Sources */,
CEF7F5ED2AEEDCC400E34952 /* VMCursor.m in Sources */,
CEF7F5EE2AEEDCC400E34952 /* VMConfigDriveDetailsView.swift in Sources */,
CEF7F5F02AEEDCC400E34952 /* NumberTextField.swift in Sources */,
Expand Down