From e8882d84846b1ede5fb07c71bb9762bbaf4a52d2 Mon Sep 17 00:00:00 2001 From: Thomas Dhome-Casanova Date: Wed, 29 Jan 2025 23:36:38 -0800 Subject: [PATCH] init windowshost --- computer_use_demo/windowshost/.gitignore | 4 + computer_use_demo/windowshost/Dockerfile | 48 + computer_use_demo/windowshost/compose.yml | 23 + .../windowshost/scripts/manage_vm.ps1 | 70 ++ .../windowshost/scripts/manage_vm.sh | 77 ++ .../windowshost/vm/buildcontainer/define.sh | 410 +++++++ .../windowshost/vm/buildcontainer/entry.sh | 38 + .../windowshost/vm/buildcontainer/install.sh | 1024 +++++++++++++++++ .../windowshost/vm/buildcontainer/power.sh | 223 ++++ .../windowshost/vm/buildcontainer/samba.sh | 109 ++ .../vm/win11def/win11x64-enterprise-eval.xml | 462 ++++++++ .../windowshost/vm/win11iso/README.md | 1 + .../vm/win11setup/firstboot/install.bat | 31 + .../vm/win11setup/setupscripts/on-logon.ps1 | 7 + .../win11setup/setupscripts/server/cursor.png | Bin 0 -> 3207 bytes .../vm/win11setup/setupscripts/server/main.py | 81 ++ .../setupscripts/server/requirements.txt | 2 + .../win11setup/setupscripts/setup-tools.psm1 | 197 ++++ .../vm/win11setup/setupscripts/setup.ps1 | 392 +++++++ .../win11setup/setupscripts/tools_config.json | 71 ++ 20 files changed, 3270 insertions(+) create mode 100644 computer_use_demo/windowshost/.gitignore create mode 100644 computer_use_demo/windowshost/Dockerfile create mode 100644 computer_use_demo/windowshost/compose.yml create mode 100644 computer_use_demo/windowshost/scripts/manage_vm.ps1 create mode 100755 computer_use_demo/windowshost/scripts/manage_vm.sh create mode 100644 computer_use_demo/windowshost/vm/buildcontainer/define.sh create mode 100644 computer_use_demo/windowshost/vm/buildcontainer/entry.sh create mode 100644 computer_use_demo/windowshost/vm/buildcontainer/install.sh create mode 100644 computer_use_demo/windowshost/vm/buildcontainer/power.sh create mode 100644 computer_use_demo/windowshost/vm/buildcontainer/samba.sh create mode 100644 computer_use_demo/windowshost/vm/win11def/win11x64-enterprise-eval.xml create mode 100644 computer_use_demo/windowshost/vm/win11iso/README.md create mode 100644 computer_use_demo/windowshost/vm/win11setup/firstboot/install.bat create mode 100644 computer_use_demo/windowshost/vm/win11setup/setupscripts/on-logon.ps1 create mode 100644 computer_use_demo/windowshost/vm/win11setup/setupscripts/server/cursor.png create mode 100644 computer_use_demo/windowshost/vm/win11setup/setupscripts/server/main.py create mode 100644 computer_use_demo/windowshost/vm/win11setup/setupscripts/server/requirements.txt create mode 100644 computer_use_demo/windowshost/vm/win11setup/setupscripts/setup-tools.psm1 create mode 100644 computer_use_demo/windowshost/vm/win11setup/setupscripts/setup.ps1 create mode 100644 computer_use_demo/windowshost/vm/win11setup/setupscripts/tools_config.json diff --git a/computer_use_demo/windowshost/.gitignore b/computer_use_demo/windowshost/.gitignore new file mode 100644 index 0000000..ed5deb8 --- /dev/null +++ b/computer_use_demo/windowshost/.gitignore @@ -0,0 +1,4 @@ +vm/win11iso/custom.iso +vm/win11storage +vm/win11setup/setupscripts/firstboot_log.txt +vm/win11setup/setupscripts/server/server.log diff --git a/computer_use_demo/windowshost/Dockerfile b/computer_use_demo/windowshost/Dockerfile new file mode 100644 index 0000000..b464714 --- /dev/null +++ b/computer_use_demo/windowshost/Dockerfile @@ -0,0 +1,48 @@ +ARG VERSION_ARG="latest" +FROM scratch AS build-amd64 + +COPY --from=qemux/qemu-docker:6.08 / / + +ARG DEBCONF_NOWARNINGS="yes" +ARG DEBIAN_FRONTEND="noninteractive" +ARG DEBCONF_NONINTERACTIVE_SEEN="true" + +RUN set -eu && \ + apt-get update && \ + apt-get --no-install-recommends -y install \ + bc \ + jq \ + curl \ + 7zip \ + wsdd \ + samba \ + xz-utils \ + wimtools \ + dos2unix \ + cabextract \ + genisoimage \ + libxml2-utils \ + libarchive-tools && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* + +COPY --chmod=755 ./vm/buildcontainer /run/ +RUN dos2unix /run/* + +COPY --chmod=755 ./vm/win11def /run/assets +RUN dos2unix /run/assets/* + +ADD --chmod=755 https://raw.githubusercontent.com/christgau/wsdd/v0.8/src/wsdd.py /usr/sbin/wsdd +ADD --chmod=664 https://github.com/qemus/virtiso-whql/releases/download/v1.9.43-0/virtio-win-1.9.43.tar.xz /drivers.txz + +FROM dockurr/windows-arm:${VERSION_ARG} AS build-arm64 +FROM build-${TARGETARCH} + +ARG VERSION_ARG="0.00" +RUN echo "$VERSION_ARG" > /run/version + +EXPOSE 8006 3389 + +ENV VERSION="win11e" + +ENTRYPOINT ["/usr/bin/tini", "-s", "/run/entry.sh"] \ No newline at end of file diff --git a/computer_use_demo/windowshost/compose.yml b/computer_use_demo/windowshost/compose.yml new file mode 100644 index 0000000..9ad6b90 --- /dev/null +++ b/computer_use_demo/windowshost/compose.yml @@ -0,0 +1,23 @@ +services: + windows: + image: windows-local + container_name: omni-windows + privileged: true + environment: + RAM_SIZE: "8G" + CPU_CORES: "4" + DISK_SIZE: "20G" + devices: + - /dev/kvm + - /dev/net/tun + cap_add: + - NET_ADMIN + ports: + - 8006:8006 # Web Viewer access + - 5000:5000 # Computer control server + volumes: + - ./vm/win11iso/custom.iso:/custom.iso + - ./vm/win11setup/firstboot:/oem + - ./vm/win11setup/setupscripts:/data + - ./vm/win11storage:/storage + \ No newline at end of file diff --git a/computer_use_demo/windowshost/scripts/manage_vm.ps1 b/computer_use_demo/windowshost/scripts/manage_vm.ps1 new file mode 100644 index 0000000..958c095 --- /dev/null +++ b/computer_use_demo/windowshost/scripts/manage_vm.ps1 @@ -0,0 +1,70 @@ +function Create-VM { + if (-not (docker images windows-local -q)) { + Write-Host "Image not found locally. Building..." + docker build -t windows-local .. + } else { + Write-Host "Image found locally. Skipping build." + } + + docker compose -f ../compose.yml up -d + + while ($true) { + try { + $response = Invoke-WebRequest -Uri "http://localhost:5000/probe" -Method GET -UseBasicParsing + if ($response.StatusCode -eq 200) { + break + } + } catch { + Write-Host "Waiting for a response from the computer control server. When first building the VM storage folder this can take a while..." + Start-Sleep -Seconds 5 + } + } + + Write-Host "VM + server is up and running!" +} + +function Start-LocalVM { + Write-Host "Starting VM..." + docker compose -f ../compose.yml start + while ($true) { + try { + $response = Invoke-WebRequest -Uri "http://localhost:5000/probe" -Method GET -UseBasicParsing + if ($response.StatusCode -eq 200) { + break + } + } catch { + Write-Host "Waiting for a response from the computer control server" + Start-Sleep -Seconds 5 + } + } + Write-Host "VM started" +} + +function Stop-LocalVM { + Write-Host "Stopping VM..." + docker compose -f ../compose.yml stop + Write-Host "VM stopped" +} + +function Remove-VM { + Write-Host "Removing VM and associated containers..." + docker compose -f ../compose.yml down + Write-Host "VM removed" +} + +if (-not $args[0]) { + Write-Host "Usage: $($MyInvocation.MyCommand.Name) [create|start|stop|delete]" + exit 1 +} + +switch ($args[0]) { + "create" { Create-VM } + "start" { Start-LocalVM } + "stop" { Stop-LocalVM } + "delete" { Remove-VM } + default { + Write-Host "Invalid option: $($args[0])" + Write-Host "Usage: $($MyInvocation.MyCommand.Name) [create|start|stop|delete]" + exit 1 + } +} \ No newline at end of file diff --git a/computer_use_demo/windowshost/scripts/manage_vm.sh b/computer_use_demo/windowshost/scripts/manage_vm.sh new file mode 100755 index 0000000..6acf45a --- /dev/null +++ b/computer_use_demo/windowshost/scripts/manage_vm.sh @@ -0,0 +1,77 @@ +#!/bin/bash + +create_vm() { + if ! docker images windows-local -q | grep -q .; then + echo "Image not found locally. Building..." + docker build -t windows-local .. + else + echo "Image found locally. Skipping build." + fi + + docker compose -f ../compose.yml up -d + + # Wait for the VM to start up + while true; do + response=$(curl --write-out '%{http_code}' --silent --output /dev/null localhost:5000/probe) + if [ $response -eq 200 ]; then + break + fi + echo "Waiting for a response from the computer control server. When first building the VM storage folder this can take a while..." + sleep 5 + done + + echo "VM + server is up and running!" +} + +start_vm() { + echo "Starting VM..." + docker compose -f ../compose.yml start + while true; do + response=$(curl --write-out '%{http_code}' --silent --output /dev/null localhost:5000/probe) + if [ $response -eq 200 ]; then + break + fi + echo "Waiting for a response from the computer control server" + sleep 5 + done + echo "VM started" +} + +stop_vm() { + echo "Stopping VM..." + docker compose -f ../compose.yml stop + echo "VM stopped" +} + +delete_vm() { + echo "Removing VM and associated containers..." + docker compose -f ../compose.yml down + echo "VM removed" +} + +# Check if control parameter is provided +if [ -z "$1" ]; then + echo "Usage: $0 [create|start|stop|delete]" + exit 1 +fi + +# Execute the appropriate function based on the control parameter +case "$1" in + "create") + create_vm + ;; + "start") + start_vm + ;; + "stop") + stop_vm + ;; + "delete") + delete_vm + ;; + *) + echo "Invalid option: $1" + echo "Usage: $0 [create|start|stop|delete]" + exit 1 + ;; +esac \ No newline at end of file diff --git a/computer_use_demo/windowshost/vm/buildcontainer/define.sh b/computer_use_demo/windowshost/vm/buildcontainer/define.sh new file mode 100644 index 0000000..f5641ed --- /dev/null +++ b/computer_use_demo/windowshost/vm/buildcontainer/define.sh @@ -0,0 +1,410 @@ +#!/usr/bin/env bash +set -Eeuo pipefail + +: "${WIDTH:=""}" +: "${HEIGHT:=""}" +: "${VERIFY:=""}" +: "${REGION:=""}" +: "${MANUAL:=""}" +: "${REMOVE:=""}" +: "${VERSION:=""}" +: "${DETECTED:=""}" +: "${KEYBOARD:=""}" +: "${LANGUAGE:=""}" +: "${USERNAME:=""}" +: "${PASSWORD:=""}" + +MIRRORS=4 +PLATFORM="x64" + +parseVersion() { + + if [[ "${VERSION}" == \"*\" || "${VERSION}" == \'*\' ]]; then + VERSION="${VERSION:1:-1}" + fi + + [ -z "$VERSION" ] && VERSION="win11" + + case "${VERSION,,}" in + "11" | "11p" | "win11" | "pro11" | "win11p" | "windows11" | "windows 11" ) + VERSION="win11x64" + ;; + "11e" | "win11e" | "windows11e" | "windows 11e" | "win11x64-enterprise-eval" ) + VERSION="win11x64-enterprise-eval" + ;; + esac + + return 0 +} + +getLanguage() { + + local id="$1" + local ret="$2" + local lang="" + local desc="" + local culture="" + + case "${id,,}" in + "ar" | "ar-"* ) + lang="Arabic" + desc="$lang" + culture="ar-SA" ;; + "bg" | "bg-"* ) + lang="Bulgarian" + desc="$lang" + culture="bg-BG" ;; + "cs" | "cs-"* | "cz" | "cz-"* ) + lang="Czech" + desc="$lang" + culture="cs-CZ" ;; + "da" | "da-"* | "dk" | "dk-"* ) + lang="Danish" + desc="$lang" + culture="da-DK" ;; + "de" | "de-"* ) + lang="German" + desc="$lang" + culture="de-DE" ;; + "el" | "el-"* | "gr" | "gr-"* ) + lang="Greek" + desc="$lang" + culture="el-GR" ;; + "gb" | "en-gb" ) + lang="English International" + desc="English" + culture="en-GB" ;; + "en" | "en-"* ) + lang="English" + desc="English" + culture="en-US" ;; + "mx" | "es-mx" ) + lang="Spanish (Mexico)" + desc="Spanish" + culture="es-MX" ;; + "es" | "es-"* ) + lang="Spanish" + desc="$lang" + culture="es-ES" ;; + "et" | "et-"* ) + lang="Estonian" + desc="$lang" + culture="et-EE" ;; + "fi" | "fi-"* ) + lang="Finnish" + desc="$lang" + culture="fi-FI" ;; + "ca" | "fr-ca" ) + lang="French Canadian" + desc="French" + culture="fr-CA" ;; + "fr" | "fr-"* ) + lang="French" + desc="$lang" + culture="fr-FR" ;; + "he" | "he-"* | "il" | "il-"* ) + lang="Hebrew" + desc="$lang" + culture="he-IL" ;; + "hr" | "hr-"* | "cr" | "cr-"* ) + lang="Croatian" + desc="$lang" + culture="hr-HR" ;; + "hu" | "hu-"* ) + lang="Hungarian" + desc="$lang" + culture="hu-HU" ;; + "it" | "it-"* ) + lang="Italian" + desc="$lang" + culture="it-IT" ;; + "ja" | "ja-"* | "jp" | "jp-"* ) + lang="Japanese" + desc="$lang" + culture="ja-JP" ;; + "ko" | "ko-"* | "kr" | "kr-"* ) + lang="Korean" + desc="$lang" + culture="ko-KR" ;; + "lt" | "lt-"* ) + lang="Lithuanian" + desc="$lang" + culture="lv-LV" ;; + "lv" | "lv-"* ) + lang="Latvian" + desc="$lang" + culture="lt-LT" ;; + "nb" | "nb-"* |"nn" | "nn-"* | "no" | "no-"* ) + lang="Norwegian" + desc="$lang" + culture="nb-NO" ;; + "nl" | "nl-"* ) + lang="Dutch" + desc="$lang" + culture="nl-NL" ;; + "pl" | "pl-"* ) + lang="Polish" + desc="$lang" + culture="pl-PL" ;; + "br" | "pt-br" ) + lang="Brazilian Portuguese" + desc="Portuguese" + culture="pt-BR" ;; + "pt" | "pt-"* ) + lang="Portuguese" + desc="$lang" + culture="pt-BR" ;; + "ro" | "ro-"* ) + lang="Romanian" + desc="$lang" + culture="ro-RO" ;; + "ru" | "ru-"* ) + lang="Russian" + desc="$lang" + culture="ru-RU" ;; + "sk" | "sk-"* ) + lang="Slovak" + desc="$lang" + culture="sk-SK" ;; + "sl" | "sl-"* | "si" | "si-"* ) + lang="Slovenian" + desc="$lang" + culture="sl-SI" ;; + "sr" | "sr-"* ) + lang="Serbian Latin" + desc="Serbian" + culture="sr-Latn-RS" ;; + "sv" | "sv-"* | "se" | "se-"* ) + lang="Swedish" + desc="$lang" + culture="sv-SE" ;; + "th" | "th-"* ) + lang="Thai" + desc="$lang" + culture="th-TH" ;; + "tr" | "tr-"* ) + lang="Turkish" + desc="$lang" + culture="tr-TR" ;; + "ua" | "ua-"* | "uk" | "uk-"* ) + lang="Ukrainian" + desc="$lang" + culture="uk-UA" ;; + "hk" | "zh-hk" | "cn-hk" ) + lang="Chinese (Traditional)" + desc="Chinese HK" + culture="zh-TW" ;; + "tw" | "zh-tw" | "cn-tw" ) + lang="Chinese (Traditional)" + desc="Chinese TW" + culture="zh-TW" ;; + "zh" | "zh-"* | "cn" | "cn-"* ) + lang="Chinese (Simplified)" + desc="Chinese" + culture="zh-CN" ;; + esac + + case "${ret,,}" in + "desc" ) echo "$desc" ;; + "name" ) echo "$lang" ;; + "culture" ) echo "$culture" ;; + *) echo "$desc";; + esac + + return 0 +} + +parseLanguage() { + + REGION="${REGION//_/-/}" + KEYBOARD="${KEYBOARD//_/-/}" + LANGUAGE="${LANGUAGE//_/-/}" + + [ -z "$LANGUAGE" ] && LANGUAGE="en" + + case "${LANGUAGE,,}" in + "arabic" | "arab" ) LANGUAGE="ar" ;; + "bulgarian" | "bu" ) LANGUAGE="bg" ;; + "chinese" | "cn" ) LANGUAGE="zh" ;; + "croatian" | "cr" | "hrvatski" ) LANGUAGE="hr" ;; + "czech" | "cz" | "cesky" ) LANGUAGE="cs" ;; + "danish" | "dk" | "danske" ) LANGUAGE="da" ;; + "dutch" | "nederlands" ) LANGUAGE="nl" ;; + "english" | "gb" | "british" ) LANGUAGE="en" ;; + "estonian" | "eesti" ) LANGUAGE="et" ;; + "finnish" | "suomi" ) LANGUAGE="fi" ;; + "french" | "français" | "francais" ) LANGUAGE="fr" ;; + "german" | "deutsch" ) LANGUAGE="de" ;; + "greek" | "gr" ) LANGUAGE="el" ;; + "hebrew" | "il" ) LANGUAGE="he" ;; + "hungarian" | "magyar" ) LANGUAGE="hu" ;; + "italian" | "italiano" ) LANGUAGE="it" ;; + "japanese" | "jp" ) LANGUAGE="ja" ;; + "korean" | "kr" ) LANGUAGE="ko" ;; + "latvian" | "latvijas" ) LANGUAGE="lv" ;; + "lithuanian" | "lietuvos" ) LANGUAGE="lt" ;; + "norwegian" | "no" | "nb" | "norsk" ) LANGUAGE="nn" ;; + "polish" | "polski" ) LANGUAGE="pl" ;; + "portuguese" | "pt" | "br" ) LANGUAGE="pt-br" ;; + "português" | "portugues" ) LANGUAGE="pt-br" ;; + "romanian" | "română" | "romana" ) LANGUAGE="ro" ;; + "russian" | "ruski" ) LANGUAGE="ru" ;; + "serbian" | "serbian latin" ) LANGUAGE="sr" ;; + "slovak" | "slovenský" | "slovensky" ) LANGUAGE="sk" ;; + "slovenian" | "si" | "slovenski" ) LANGUAGE="sl" ;; + "spanish" | "espanol" | "español" ) LANGUAGE="es" ;; + "swedish" | "se" | "svenska" ) LANGUAGE="sv" ;; + "turkish" | "türk" | "turk" ) LANGUAGE="tr" ;; + "thai" ) LANGUAGE="th" ;; + "ukrainian" | "ua" ) LANGUAGE="uk" ;; + esac + + local culture + culture=$(getLanguage "$LANGUAGE" "culture") + [ -n "$culture" ] && return 0 + + error "Invalid LANGUAGE specified, value \"$LANGUAGE\" is not recognized!" + return 1 +} + +printVersion() { + + local id="$1" + local desc="$2" + + case "${id,,}" in + "win11"* ) desc="Windows 11" ;; + esac + + if [ -z "$desc" ]; then + desc="Windows" + [[ "${PLATFORM,,}" != "x64" ]] && desc+=" for ${PLATFORM}" + fi + + echo "$desc" + return 0 +} + +printEdition() { + + local id="$1" + local desc="$2" + local result="" + local edition="" + + result=$(printVersion "$id" "x") + [[ "$result" == "x" ]] && echo "$desc" && return 0 + + case "${id,,}" in + *"-enterprise" ) + edition="Enterprise" + ;; + *"-enterprise-eval" ) + edition="Enterprise (Evaluation)" + ;; + esac + + [ -n "$edition" ] && result+=" $edition" + + echo "$result" + return 0 +} + +fromName() { + + local id="" + local name="$1" + local arch="$2" + + local add="" + [[ "$arch" != "x64" ]] && add="$arch" + + case "${name,,}" in + *"windows 11"* ) id="win11${arch}" ;; + esac + + echo "$id" + return 0 +} + +getVersion() { + + local id + local name="$1" + local arch="$2" + + id=$(fromName "$name" "$arch") + + case "${id,,}" in + "win11"* ) + case "${name,,}" in + *" enterprise evaluation"* ) id="$id-enterprise-eval" ;; + *" enterprise"* ) id="$id-enterprise" ;; + esac + ;; + esac + + echo "$id" + return 0 +} + +addFolder() { + + local src="$1" + local folder="/oem" + + [ ! -d "$folder" ] && folder="/OEM" + [ ! -d "$folder" ] && folder="$STORAGE/oem" + [ ! -d "$folder" ] && folder="$STORAGE/OEM" + [ ! -d "$folder" ] && return 0 + + local msg="Adding OEM folder to image..." + info "$msg" && html "$msg" + + local dest="$src/\$OEM\$/\$1/OEM" + mkdir -p "$dest" || return 1 + cp -Lr "$folder/." "$dest" || return 1 + + local file + file=$(find "$dest" -maxdepth 1 -type f -iname install.bat | head -n 1) + [ -f "$file" ] && unix2dos -q "$file" + + return 0 +} + +# migrateFiles() { + +# local base="$1" +# local version="$2" +# local file="" + +# [ -f "$base" ] && return 0 + +# [[ "${version,,}" == "tiny10" ]] && file="tiny10_x64_23h2.iso" +# [[ "${version,,}" == "tiny11" ]] && file="tiny11_2311_x64.iso" +# [[ "${version,,}" == "core11" ]] && file="tiny11_core_x64_beta_1.iso" +# [[ "${version,,}" == "winxpx86" ]] && file="en_windows_xp_professional_with_service_pack_3_x86_cd_x14-80428.iso" +# [[ "${version,,}" == "winvistax64" ]] && file="en_windows_vista_sp2_x64_dvd_342267.iso" +# [[ "${version,,}" == "win7x64" ]] && file="en_windows_7_enterprise_with_sp1_x64_dvd_u_677651.iso" + +# [ ! -f "$STORAGE/$file" ] && return 0 +# mv -f "$STORAGE/$file" "$base" || return 1 + +# return 0 +# } + +migrateFiles() { + + local base="$1" + local version="$2" + local file="" + + [ -f "$base" ] && return 0 + + [ ! -f "$STORAGE/$file" ] && return 0 + mv -f "$STORAGE/$file" "$base" || return 1 + + return 0 +} + +return 0 diff --git a/computer_use_demo/windowshost/vm/buildcontainer/entry.sh b/computer_use_demo/windowshost/vm/buildcontainer/entry.sh new file mode 100644 index 0000000..7cfbe70 --- /dev/null +++ b/computer_use_demo/windowshost/vm/buildcontainer/entry.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash +set -Eeuo pipefail + +: "${BOOT_MODE:="windows"}" + +APP="OmniParser Windows" +SUPPORT="https://github.com/microsoft/OmniParser" + +cd /run + +. reset.sh # Initialize system +. define.sh # Define versions +. install.sh # Run installation +. disk.sh # Initialize disks +. display.sh # Initialize graphics +. network.sh # Initialize network +. samba.sh # Configure samba +. boot.sh # Configure boot +. proc.sh # Initialize processor +. power.sh # Configure shutdown +. config.sh # Configure arguments + +trap - ERR + +version=$(qemu-system-x86_64 --version | head -n 1 | cut -d '(' -f 1 | awk '{ print $NF }') +info "Booting ${APP}${BOOT_DESC} using QEMU v$version..." + +{ qemu-system-x86_64 ${ARGS:+ $ARGS} >"$QEMU_OUT" 2>"$QEMU_LOG"; rc=$?; } || : +(( rc != 0 )) && error "$(<"$QEMU_LOG")" && exit 15 + +terminal +( sleep 30; boot ) & +tail -fn +0 "$QEMU_LOG" 2>/dev/null & +cat "$QEMU_TERM" 2> /dev/null | tee "$QEMU_PTY" & +wait $! || : + +sleep 1 & wait $! +[ ! -f "$QEMU_END" ] && finish 0 diff --git a/computer_use_demo/windowshost/vm/buildcontainer/install.sh b/computer_use_demo/windowshost/vm/buildcontainer/install.sh new file mode 100644 index 0000000..d65ae6b --- /dev/null +++ b/computer_use_demo/windowshost/vm/buildcontainer/install.sh @@ -0,0 +1,1024 @@ +#!/usr/bin/env bash +set -Eeuo pipefail + +TMP="$STORAGE/tmp" +DIR="$TMP/unpack" +FB="falling back to manual installation!" +ETFS="boot/etfsboot.com" +EFISYS="efi/microsoft/boot/efisys_noprompt.bin" + +skipInstall() { + + local iso="$1" + local magic byte + local boot="$STORAGE/windows.boot" + local previous="$STORAGE/windows.base" + + if [ -f "$previous" ]; then + previous=$(<"$previous") + if [ -n "$previous" ]; then + previous="$STORAGE/$previous" + if [[ "${previous,,}" != "${iso,,}" ]]; then + if [ -f "$boot" ] && hasDisk; then + info "Detected that the version was changed, but ignoring this because Windows is already installed." + info "Please start with an empty /storage folder, if you want to install a different version of Windows." + return 0 + fi + [ -f "$previous" ] && rm -f "$previous" + return 1 + fi + fi + fi + + [ -f "$boot" ] && hasDisk && return 0 + + [ ! -f "$iso" ] && return 1 + [ ! -s "$iso" ] && return 1 + + # Check if the ISO was already processed by our script + magic=$(dd if="$iso" seek=0 bs=1 count=1 status=none | tr -d '\000') + magic="$(printf '%s' "$magic" | od -A n -t x1 -v | tr -d ' \n')" + byte="16" && [[ "$MANUAL" == [Yy1]* ]] && byte="17" + + if [[ "$magic" != "$byte" ]]; then + info "The ISO will be processed again because the configuration was changed..." + return 1 + fi + + return 0 +} + +startInstall() { + + html "Starting $APP..." + + if [ -z "$CUSTOM" ]; then + + local file="${VERSION//\//}.iso" + + if [[ "${VERSION,,}" == "http"* ]]; then + + file=$(basename "${VERSION%%\?*}") + : "${file//+/ }"; printf -v file '%b' "${_//%/\\x}" + file=$(echo "$file" | sed -e 's/[^A-Za-z0-9._-]/_/g') + + else + + local language + language=$(getLanguage "$LANGUAGE" "culture") + language="${language%%-*}" + + if [ -n "$language" ] && [[ "${language,,}" != "en" ]]; then + file="${VERSION//\//}_${language,,}.iso" + fi + + fi + + BOOT="$STORAGE/$file" + + ! migrateFiles "$BOOT" "$VERSION" && error "Migration failed!" && exit 57 + + fi + + skipInstall "$BOOT" && return 1 + + rm -rf "$TMP" + mkdir -p "$TMP" + + if [ -z "$CUSTOM" ]; then + + ISO=$(basename "$BOOT") + ISO="$TMP/$ISO" + + if [ -f "$BOOT" ] && [ -s "$BOOT" ]; then + mv -f "$BOOT" "$ISO" + fi + + fi + + rm -f "$BOOT" + return 0 +} + +finishInstall() { + + local iso="$1" + local aborted="$2" + local base byte + + if [ ! -s "$iso" ] || [ ! -f "$iso" ]; then + error "Failed to find ISO file: $iso" && return 1 + fi + + if [[ "$aborted" != [Yy1]* ]]; then + # Mark ISO as prepared via magic byte + byte="16" && [[ "$MANUAL" == [Yy1]* ]] && byte="17" + if ! printf '%b' "\x$byte" | dd of="$iso" bs=1 seek=0 count=1 conv=notrunc status=none; then + warn "failed to set magic byte in ISO file: $iso" + fi + fi + + rm -f "$STORAGE/windows.old" + rm -f "$STORAGE/windows.vga" + rm -f "$STORAGE/windows.args" + rm -f "$STORAGE/windows.base" + rm -f "$STORAGE/windows.boot" + rm -f "$STORAGE/windows.mode" + rm -f "$STORAGE/windows.type" + + cp -f /run/version "$STORAGE/windows.ver" + + if [[ "$iso" == "$STORAGE/"* ]]; then + if [[ "$aborted" != [Yy1]* ]] || [ -z "$CUSTOM" ]; then + base=$(basename "$iso") + echo "$base" > "$STORAGE/windows.base" + fi + fi + + if [[ "${PLATFORM,,}" == "x64" ]]; then + if [[ "${BOOT_MODE,,}" == "windows_legacy" ]]; then + echo "$BOOT_MODE" > "$STORAGE/windows.mode" + if [[ "${MACHINE,,}" != "q35" ]]; then + echo "$MACHINE" > "$STORAGE/windows.old" + fi + else + # Enable secure boot + TPM on manual installs as Win11 requires + if [[ "$MANUAL" == [Yy1]* ]] || [[ "$aborted" == [Yy1]* ]]; then + if [[ "${DETECTED,,}" == "win11"* ]]; then + BOOT_MODE="windows_secure" + echo "$BOOT_MODE" > "$STORAGE/windows.mode" + fi + fi + # Enable secure boot on multi-socket systems to workaround freeze + if [ -n "$SOCKETS" ] && [[ "$SOCKETS" != "1" ]]; then + BOOT_MODE="windows_secure" + echo "$BOOT_MODE" > "$STORAGE/windows.mode" + fi + fi + fi + + if [ -n "${ARGS:-}" ]; then + ARGUMENTS="$ARGS ${ARGUMENTS:-}" + echo "$ARGS" > "$STORAGE/windows.args" + fi + + if [ -n "${DISK_TYPE:-}" ] && [[ "${DISK_TYPE:-}" != "scsi" ]]; then + echo "$DISK_TYPE" > "$STORAGE/windows.type" + fi + + rm -rf "$TMP" + return 0 +} + +abortInstall() { + + local dir="$1" + local iso="$2" + local efi + + [[ "${iso,,}" == *".esd" ]] && exit 60 + + efi=$(find "$dir" -maxdepth 1 -type d -iname efi | head -n 1) + + if [ -z "$efi" ]; then + [[ "${PLATFORM,,}" == "x64" ]] && BOOT_MODE="windows_legacy" + fi + + if [ -n "$CUSTOM" ]; then + BOOT="$iso" + REMOVE="N" + else + if [[ "$iso" != "$BOOT" ]]; then + if ! mv -f "$iso" "$BOOT"; then + error "Failed to move ISO file: $iso" && return 1 + fi + fi + fi + + finishInstall "$BOOT" "Y" && return 0 + return 1 +} + +detectCustom() { + + local file base + CUSTOM="" + + file=$(find / -maxdepth 1 -type f -iname custom.iso | head -n 1) + [ ! -s "$file" ] && file=$(find "$STORAGE" -maxdepth 1 -type f -iname custom.iso | head -n 1) + + if [ ! -s "$file" ] && [[ "${VERSION,,}" != "http"* ]]; then + base=$(basename "$VERSION") + file="$STORAGE/$base" + fi + + if [ ! -f "$file" ] || [ ! -s "$file" ]; then + return 0 + fi + + local size + size="$(stat -c%s "$file")" + [ -z "$size" ] || [[ "$size" == "0" ]] && return 0 + + ISO="$file" + CUSTOM="$ISO" + BOOT="$STORAGE/windows.$size.iso" + + return 0 +} + +extractESD() { + + local iso="$1" + local dir="$2" + local version="$3" + local desc="$4" + local size size_gb space space_gb desc + + local msg="Extracting $desc bootdisk..." + info "$msg" && html "$msg" + + if [ "$(stat -c%s "$iso")" -lt 100000000 ]; then + error "Invalid ESD file: Size is smaller than 100 MB" && return 1 + fi + + rm -rf "$dir" + mkdir -p "$dir" + + size=16106127360 + size_gb=$(( (size + 1073741823)/1073741824 )) + space=$(df --output=avail -B 1 "$dir" | tail -n 1) + space_gb=$(( (space + 1073741823)/1073741824 )) + + if (( size > space )); then + error "Not enough free space in $STORAGE, have $space_gb GB available but need at least $size_gb GB." && return 1 + fi + + local esdImageCount + esdImageCount=$(wimlib-imagex info "$iso" | awk '/Image Count:/ {print $3}') + + wimlib-imagex apply "$iso" 1 "$dir" --quiet 2>/dev/null || { + retVal=$? + error "Extracting $desc bootdisk failed" && return $retVal + } + + local bootWimFile="$dir/sources/boot.wim" + local installWimFile="$dir/sources/install.wim" + + local msg="Extracting $desc environment..." + info "$msg" && html "$msg" + + wimlib-imagex export "$iso" 2 "$bootWimFile" --compress=none --quiet || { + retVal=$? + error "Adding WinPE failed" && return ${retVal} + } + + local msg="Extracting $desc setup..." + info "$msg" && html "$msg" + + wimlib-imagex export "$iso" 3 "$bootWimFile" --compress=none --boot --quiet || { + retVal=$? + error "Adding Windows Setup failed" && return ${retVal} + } + + if [[ "${PLATFORM,,}" == "x64" ]]; then + LABEL="CCCOMA_X64FRE_EN-US_DV9" + else + LABEL="CPBA_A64FRE_EN-US_DV9" + fi + + local msg="Extracting $desc image..." + info "$msg" && html "$msg" + + local edition imageIndex imageEdition + edition=$(getCatalog "$version" "name") + + if [ -z "$edition" ]; then + error "Invalid VERSION specified, value \"$version\" is not recognized!" && return 1 + fi + + for (( imageIndex=4; imageIndex<=esdImageCount; imageIndex++ )); do + imageEdition=$(wimlib-imagex info "$iso" ${imageIndex} | grep '^Description:' | sed 's/Description:[ \t]*//') + [[ "${imageEdition,,}" != "${edition,,}" ]] && continue + wimlib-imagex export "$iso" ${imageIndex} "$installWimFile" --compress=LZMS --chunk-size 128K --quiet || { + retVal=$? + error "Addition of $imageIndex to the $desc image failed" && return $retVal + } + return 0 + done + + error "Failed to find product '$edition' in install.wim!" && return 1 +} + +extractImage() { + + local iso="$1" + local dir="$2" + local version="$3" + local desc="local ISO" + local size size_gb space space_gb + + if [ -z "$CUSTOM" ]; then + desc="downloaded ISO" + if [[ "$version" != "http"* ]]; then + desc=$(printVersion "$version" "$desc") + fi + fi + + if [[ "${iso,,}" == *".esd" ]]; then + extractESD "$iso" "$dir" "$version" "$desc" && return 0 + return 1 + fi + + local msg="Extracting $desc image..." + info "$msg" && html "$msg" + + rm -rf "$dir" + mkdir -p "$dir" + + size=$(stat -c%s "$iso") + size_gb=$(( (size + 1073741823)/1073741824 )) + space=$(df --output=avail -B 1 "$dir" | tail -n 1) + space_gb=$(( (space + 1073741823)/1073741824 )) + + if ((size<100000000)); then + error "Invalid ISO file: Size is smaller than 100 MB" && return 1 + fi + + if (( size > space )); then + error "Not enough free space in $STORAGE, have $space_gb GB available but need at least $size_gb GB." && return 1 + fi + + rm -rf "$dir" + + if ! 7z x "$iso" -o"$dir" > /dev/null; then + error "Failed to extract ISO file: $iso" && return 1 + fi + + LABEL=$(isoinfo -d -i "$iso" | sed -n 's/Volume id: //p') + + return 0 +} + +getPlatform() { + + local xml="$1" + local tag="ARCH" + local platform="x64" + local arch + + arch=$(sed -n "/$tag/{s/.*<$tag>\(.*\)<\/$tag>.*/\1/;p}" <<< "$xml") + + case "${arch,,}" in + "0" ) platform="x86" ;; + "9" ) platform="x64" ;; + "12" )platform="arm64" ;; + esac + + echo "$platform" + return 0 +} + +checkPlatform() { + + local xml="$1" + local platform compat + + platform=$(getPlatform "$xml") + + case "${platform,,}" in + "x86" ) compat="x64" ;; + "x64" ) compat="$platform" ;; + "arm64" ) compat="$platform" ;; + * ) compat="${PLATFORM,,}" ;; + esac + + [[ "${compat,,}" == "${PLATFORM,,}" ]] && return 0 + + error "You cannot boot ${platform^^} images on a $PLATFORM CPU!" + return 1 +} + +hasVersion() { + + local id="$1" + local tag="$2" + local xml="$3" + local edition + + [ ! -f "/run/assets/$id.xml" ] && return 1 + + edition=$(printEdition "$id" "") + [ -z "$edition" ] && return 1 + [[ "${xml,,}" != *"<${tag,,}>${edition,,}"* ]] && return 1 + + return 0 +} + +selectVersion() { + + local tag="$1" + local xml="$2" + local platform="$3" + local id name prefer + + name=$(sed -n "/$tag/{s/.*<$tag>\(.*\)<\/$tag>.*/\1/;p}" <<< "$xml") + [[ "$name" == *"Operating System"* ]] && name="" + [ -z "$name" ] && return 0 + + id=$(fromName "$name" "$platform") + [ -z "$id" ] && warn "Unknown ${tag,,}: '$name'" && return 0 + + prefer="$id-enterprise" + hasVersion "$prefer" "$tag" "$xml" && echo "$prefer" && return 0 + + prefer="$id" + hasVersion "$prefer" "$tag" "$xml" && echo "$prefer" && return 0 + + prefer=$(getVersion "$name" "$platform") + + echo "$prefer" + return 0 +} + +detectVersion() { + + local xml="$1" + local id platform + + platform=$(getPlatform "$xml") + id=$(selectVersion "DISPLAYNAME" "$xml" "$platform") + [ -z "$id" ] && id=$(selectVersion "PRODUCTNAME" "$xml" "$platform") + [ -z "$id" ] && id=$(selectVersion "NAME" "$xml" "$platform") + + echo "$id" + return 0 +} + +detectLanguage() { + + local xml="$1" + local lang="" + + if [[ "$xml" == *"LANGUAGE>"* ]]; then + lang="${xml#*LANGUAGE>}" + lang="${lang%%<*}" + else + if [[ "$xml" == *"FALLBACK>"* ]]; then + lang="${xml#*FALLBACK>}" + lang="${lang%%<*}" + fi + fi + + if [ -z "$lang" ]; then + warn "Language could not be detected from ISO!" && return 0 + fi + + local culture + culture=$(getLanguage "$lang" "culture") + [ -n "$culture" ] && LANGUAGE="$lang" && return 0 + + warn "Invalid language detected: \"$lang\"" + return 0 +} + +setXML() { + + local file="/custom.xml" + + [ ! -f "$file" ] || [ ! -s "$file" ] && file="$STORAGE/custom.xml" + [ ! -f "$file" ] || [ ! -s "$file" ] && file="/run/assets/custom.xml" + [ ! -f "$file" ] || [ ! -s "$file" ] && file="$1" + [ ! -f "$file" ] || [ ! -s "$file" ] && file="/run/assets/$DETECTED.xml" + [ ! -f "$file" ] || [ ! -s "$file" ] && return 1 + + XML="$file" + return 0 +} + +detectImage() { + + local dir="$1" + local version="$2" + local desc msg find language + + XML="" + + if [ -z "$DETECTED" ] && [ -z "$CUSTOM" ]; then + [[ "${version,,}" != "http"* ]] && DETECTED="$version" + fi + + if [ -n "$DETECTED" ]; then + + if ! setXML "" && [[ "$MANUAL" != [Yy1]* ]]; then + MANUAL="Y" + desc=$(printEdition "$DETECTED" "this version") + warn "the answer file for $desc was not found ($DETECTED.xml), $FB." + fi + + return 0 + fi + + info "Detecting version from ISO image..." + + local src wim info + src=$(find "$dir" -maxdepth 1 -type d -iname sources | head -n 1) + + if [ ! -d "$src" ]; then + warn "failed to locate 'sources' folder in ISO image, $FB" && return 1 + fi + + wim=$(find "$src" -maxdepth 1 -type f -iname install.wim | head -n 1) + [ ! -f "$wim" ] && wim=$(find "$src" -maxdepth 1 -type f -iname install.esd | head -n 1) + + if [ ! -f "$wim" ]; then + warn "failed to locate 'install.wim' or 'install.esd' in ISO image, $FB" && return 1 + fi + + info=$(wimlib-imagex info -xml "$wim" | tr -d '\000') + checkPlatform "$info" || exit 67 + + DETECTED=$(detectVersion "$info") + + if [ -z "$DETECTED" ]; then + msg="Failed to determine Windows version from image" + if setXML "" || [[ "$MANUAL" == [Yy1]* ]]; then + info "${msg}!" + else + MANUAL="Y" + warn "${msg}, $FB." + fi + return 0 + fi + + desc=$(printEdition "$DETECTED" "$DETECTED") + detectLanguage "$info" + + if [[ "${LANGUAGE,,}" != "en" ]] && [[ "${LANGUAGE,,}" != "en-"* ]]; then + language=$(getLanguage "$LANGUAGE" "desc") + desc+=" ($language)" + fi + + info "Detected: $desc" + setXML "" && return 0 + + msg="the answer file for $desc was not found ($DETECTED.xml)" + local fallback="/run/assets/${DETECTED%%-*}.xml" + + if setXML "$fallback" || [[ "$MANUAL" == [Yy1]* ]]; then + [[ "$MANUAL" != [Yy1]* ]] && warn "${msg}." + else + MANUAL="Y" + warn "${msg}, $FB." + fi + + return 0 +} + +prepareImage() { + + local iso="$1" + local dir="$2" + local desc missing + + desc=$(printVersion "$DETECTED" "$DETECTED") + + if [[ "${BOOT_MODE,,}" != "windows_legacy" ]]; then + + [ -f "$dir/$ETFS" ] && [ -f "$dir/$EFISYS" ] && return 0 + + missing=$(basename "$dir/$EFISYS") + [ ! -f "$dir/$ETFS" ] && missing=$(basename "$dir/$ETFS") + + error "Failed to locate file \"${missing,,}\" in ISO image!" + return 1 + fi + + prepareLegacy "$iso" "$dir" "$desc" && return 0 + + error "Failed to extract boot image from ISO image!" + return 1 +} + +updateXML() { + + local asset="$1" + local language="$2" + local culture region user admin pass keyboard + + [ -z "$HEIGHT" ] && HEIGHT="800" + [ -z "$WIDTH" ] && WIDTH="1280" + + sed -i "s/1080<\/VerticalResolution>/$HEIGHT<\/VerticalResolution>/g" "$asset" + sed -i "s/1920<\/HorizontalResolution>/$WIDTH<\/HorizontalResolution>/g" "$asset" + + culture=$(getLanguage "$language" "culture") + + if [ -n "$culture" ] && [[ "${culture,,}" != "en-us" ]]; then + sed -i "s/en-US<\/UILanguage>/$culture<\/UILanguage>/g" "$asset" + fi + + region="$REGION" + [ -z "$region" ] && region="$culture" + + if [ -n "$region" ] && [[ "${region,,}" != "en-us" ]]; then + sed -i "s/en-US<\/UserLocale>/$region<\/UserLocale>/g" "$asset" + sed -i "s/en-US<\/SystemLocale>/$region<\/SystemLocale>/g" "$asset" + fi + + keyboard="$KEYBOARD" + [ -z "$keyboard" ] && keyboard="$culture" + + if [ -n "$keyboard" ] && [[ "${keyboard,,}" != "en-us" ]]; then + sed -i "s/en-US<\/InputLocale>/$keyboard<\/InputLocale>/g" "$asset" + sed -i "s/0409:00000409<\/InputLocale>/$keyboard<\/InputLocale>/g" "$asset" + fi + + user=$(echo "$USERNAME" | sed 's/[^[:alnum:]@!._-]//g') + + if [ -n "$user" ]; then + sed -i "s/Docker<\/Name>/$user<\/Name>/g" "$asset" + sed -i "s/where name=\"Docker\"/where name=\"$user\"/g" "$asset" + sed -i "s/Docker<\/FullName>/$user<\/FullName>/g" "$asset" + sed -i "s/Docker<\/Username>/$user<\/Username>/g" "$asset" + fi + + if [ -n "$PASSWORD" ]; then + pass=$(printf '%s' "${PASSWORD}Password" | iconv -f utf-8 -t utf-16le | base64 -w 0) + admin=$(printf '%s' "${PASSWORD}AdministratorPassword" | iconv -f utf-8 -t utf-16le | base64 -w 0) + sed -i "s/password<\/Value>/$admin<\/Value>/g" "$asset" + sed -i "s/true<\/PlainText>/<PlainText>false<\/PlainText>/g" "$asset" + sed -z "s/<Password>...........<Value \/>/<Password>\n <Value>$pass<\/Value>/g" -i "$asset" + sed -z "s/<Password>...............<Value \/>/<Password>\n <Value>$pass<\/Value>/g" -i "$asset" + sed -z "s/<AdministratorPassword>...........<Value \/>/<AdministratorPassword>\n <Value>$admin<\/Value>/g" -i "$asset" + sed -z "s/<AdministratorPassword>...............<Value \/>/<AdministratorPassword>\n <Value>$admin<\/Value>/g" -i "$asset" + fi + + return 0 +} + +addDriver() { + + local id="$1" + local path="$2" + local target="$3" + local driver="$4" + local folder="" + + case "${id,,}" in + "win11x64"* ) folder="w11/amd64" ;; + esac + + if [ -z "$folder" ]; then + warn "no \"$driver\" driver found for \"$DETECTED\" !" && return 0 + fi + + [ ! -d "$path/$driver/$folder" ] && return 0 + + local dest="$path/$target/$driver" + mkdir -p "$dest" || return 1 + cp -Lr "$path/$driver/$folder/." "$dest" || return 1 + + return 0 +} + +addDrivers() { + + local src="$1" + local tmp="$2" + local file="$3" + local index="$4" + local version="$5" + local drivers="$tmp/drivers" + + rm -rf "$drivers" + mkdir -p "$drivers" + + local msg="Adding drivers to image..." + info "$msg" && html "$msg" + + if ! bsdtar -xf /drivers.txz -C "$drivers"; then + error "Failed to extract drivers from archive!" && return 1 + fi + + local target="\$WinPEDriver\$" + local dest="$drivers/$target" + mkdir -p "$dest" || return 1 + + wimlib-imagex update "$file" "$index" --command "delete --force --recursive /$target" >/dev/null || true + + addDriver "$version" "$drivers" "$target" "qxl" || return 1 + addDriver "$version" "$drivers" "$target" "viofs" || return 1 + addDriver "$version" "$drivers" "$target" "sriov" || return 1 + addDriver "$version" "$drivers" "$target" "smbus" || return 1 + addDriver "$version" "$drivers" "$target" "qxldod" || return 1 + addDriver "$version" "$drivers" "$target" "viorng" || return 1 + addDriver "$version" "$drivers" "$target" "viostor" || return 1 + addDriver "$version" "$drivers" "$target" "viomem" || return 1 + addDriver "$version" "$drivers" "$target" "NetKVM" || return 1 + addDriver "$version" "$drivers" "$target" "Balloon" || return 1 + addDriver "$version" "$drivers" "$target" "vioscsi" || return 1 + addDriver "$version" "$drivers" "$target" "pvpanic" || return 1 + addDriver "$version" "$drivers" "$target" "vioinput" || return 1 + addDriver "$version" "$drivers" "$target" "viogpudo" || return 1 + addDriver "$version" "$drivers" "$target" "vioserial" || return 1 + addDriver "$version" "$drivers" "$target" "qemupciserial" || return 1 + + case "${version,,}" in + "win11x64"* | "win2025"* ) + # Workaround Virtio GPU driver bug + local dst="$src/\$OEM\$/\$\$/Drivers" + mkdir -p "$dst" || return 1 + cp -Lr "$dest/." "$dst" || return 1 + rm -rf "$dest/viogpudo" + ;; + esac + + if ! wimlib-imagex update "$file" "$index" --command "add $dest /$target" >/dev/null; then + return 1 + fi + + rm -rf "$drivers" + return 0 +} + +updateImage() { + + local dir="$1" + local asset="$2" + local language="$3" + local tmp="/tmp/install" + local file="autounattend.xml" + local org="${file//.xml/.org}" + local dat="${file//.xml/.dat}" + local desc path src wim xml index result + + if [ ! -s "$asset" ] || [ ! -f "$asset" ]; then + asset="" + if [[ "$MANUAL" != [Yy1]* ]]; then + MANUAL="Y" + warn "no answer file provided, $FB." + fi + fi + + rm -rf "$tmp" + mkdir -p "$tmp" + + src=$(find "$dir" -maxdepth 1 -type d -iname sources | head -n 1) + + if [ ! -d "$src" ]; then + error "failed to locate 'sources' folder in ISO image, $FB" && return 1 + fi + + wim=$(find "$src" -maxdepth 1 -type f -iname boot.wim | head -n 1) + [ ! -f "$wim" ] && wim=$(find "$src" -maxdepth 1 -type f -iname boot.esd | head -n 1) + + if [ ! -f "$wim" ]; then + error "failed to locate 'boot.wim' or 'boot.esd' in ISO image, $FB" && return 1 + fi + + index="1" + result=$(wimlib-imagex info -xml "$wim" | tr -d '\000') + + if [[ "${result^^}" == *"<IMAGE INDEX=\"2\">"* ]]; then + index="2" + fi + + if ! addDrivers "$src" "$tmp" "$wim" "$index" "$DETECTED"; then + error "Failed to add drivers to image!" + fi + + if ! addFolder "$src"; then + error "Failed to add OEM folder to image!" + fi + + if wimlib-imagex extract "$wim" "$index" "/$file" "--dest-dir=$tmp" >/dev/null 2>&1; then + if ! wimlib-imagex extract "$wim" "$index" "/$dat" "--dest-dir=$tmp" >/dev/null 2>&1; then + if ! wimlib-imagex extract "$wim" "$index" "/$org" "--dest-dir=$tmp" >/dev/null 2>&1; then + if ! wimlib-imagex update "$wim" "$index" --command "rename /$file /$org" > /dev/null; then + warn "failed to backup original answer file ($file)." + fi + fi + fi + fi + + if [[ "$MANUAL" != [Yy1]* ]]; then + + xml=$(basename "$asset") + info "Adding $xml for automatic installation..." + + local answer="$tmp/$xml" + cp "$asset" "$answer" + updateXML "$answer" "$language" + + if ! wimlib-imagex update "$wim" "$index" --command "add $answer /$file" > /dev/null; then + MANUAL="Y" + warn "failed to add answer file ($xml) to ISO image, $FB" + else + wimlib-imagex update "$wim" "$index" --command "add $answer /$dat" > /dev/null || true + fi + + fi + + if [[ "$MANUAL" == [Yy1]* ]]; then + + wimlib-imagex update "$wim" "$index" --command "delete --force /$file" > /dev/null || true + + if wimlib-imagex extract "$wim" "$index" "/$org" "--dest-dir=$tmp" >/dev/null 2>&1; then + if ! wimlib-imagex update "$wim" "$index" --command "add $tmp/$org /$file" > /dev/null; then + warn "failed to restore original answer file ($org)." + fi + fi + + fi + + local find="$file" + [[ "$MANUAL" == [Yy1]* ]] && find="$org" + path=$(find "$dir" -maxdepth 1 -type f -iname "$find" | head -n 1) + + if [ -f "$path" ]; then + if [[ "$MANUAL" != [Yy1]* ]]; then + mv -f "$path" "${path%.*}.org" + else + mv -f "$path" "${path%.*}.xml" + fi + fi + + rm -rf "$tmp" + return 0 +} + +removeImage() { + + local iso="$1" + + [ ! -f "$iso" ] && return 0 + [ -n "$CUSTOM" ] && return 0 + + rm -f "$iso" 2> /dev/null || warn "failed to remove $iso !" + + return 0 +} + +buildImage() { + + local dir="$1" + local failed="" + local cat="BOOT.CAT" + local log="/run/shm/iso.log" + local base size size_gb space space_gb desc + + if [ -f "$BOOT" ]; then + error "File $BOOT does already exist?!" && return 1 + fi + + base=$(basename "$BOOT") + local out="$TMP/${base%.*}.tmp" + rm -f "$out" + + desc=$(printVersion "$DETECTED" "ISO") + + local msg="Building $desc image..." + info "$msg" && html "$msg" + + [ -z "$LABEL" ] && LABEL="Windows" + + if [ ! -f "$dir/$ETFS" ]; then + error "Failed to locate file \"$ETFS\" in ISO image!" && return 1 + fi + + size=$(du -h -b --max-depth=0 "$dir" | cut -f1) + size_gb=$(( (size + 1073741823)/1073741824 )) + space=$(df --output=avail -B 1 "$TMP" | tail -n 1) + space_gb=$(( (space + 1073741823)/1073741824 )) + + if (( size > space )); then + error "Not enough free space in $STORAGE, have $space_gb GB available but need at least $size_gb GB." && return 1 + fi + + ! genisoimage -o "$out" -b "$ETFS" -no-emul-boot -c "$cat" -iso-level 4 -J -l -D -N -joliet-long -relaxed-filenames -V "${LABEL::30}" \ + -udf -boot-info-table -eltorito-alt-boot -eltorito-boot "$EFISYS" -no-emul-boot -allow-limited-size -quiet "$dir" 2> "$log" && failed="y" + + if [ -n "$failed" ]; then + [ -s "$log" ] && echo "$(<"$log")" + error "Failed to build image!" && return 1 + fi + + local error="" + local hide="Warning: creating filesystem that does not conform to ISO-9660." + + [ -s "$log" ] && error="$(<"$log")" + [[ "$error" != "$hide" ]] && echo "$error" + + mv -f "$out" "$BOOT" || return 1 + return 0 +} + +bootWindows() { + + rm -rf "$TMP" + + if [ -f "$STORAGE/windows.args" ]; then + ARGS=$(<"$STORAGE/windows.args") + ARGUMENTS="$ARGS ${ARGUMENTS:-}" + fi + + if [ -s "$STORAGE/windows.type" ] && [ -f "$STORAGE/windows.type" ]; then + [ -z "${DISK_TYPE:-}" ] && DISK_TYPE=$(<"$STORAGE/windows.type") + fi + + if [ -s "$STORAGE/windows.mode" ] && [ -f "$STORAGE/windows.mode" ]; then + BOOT_MODE=$(<"$STORAGE/windows.mode") + if [ -s "$STORAGE/windows.old" ] && [ -f "$STORAGE/windows.old" ]; then + [[ "${PLATFORM,,}" == "x64" ]] && MACHINE=$(<"$STORAGE/windows.old") + fi + return 0 + fi + + # Migrations + + [[ "${PLATFORM,,}" != "x64" ]] && return 0 + + if [ -f "$STORAGE/windows.old" ]; then + MACHINE=$(<"$STORAGE/windows.old") + [ -z "$MACHINE" ] && MACHINE="q35" + BOOT_MODE="windows_legacy" + echo "$BOOT_MODE" > "$STORAGE/windows.mode" + return 0 + fi + + local creation="1.10" + local minimal="2.14" + + if [ -f "$STORAGE/windows.ver" ]; then + creation=$(<"$STORAGE/windows.ver") + [[ "${creation}" != *"."* ]] && creation="$minimal" + fi + + # Force secure boot on installs created prior to v2.14 + if (( $(echo "$creation < $minimal" | bc -l) )); then + if [[ "${BOOT_MODE,,}" == "windows" ]]; then + BOOT_MODE="windows_secure" + echo "$BOOT_MODE" > "$STORAGE/windows.mode" + if [ -f "$STORAGE/windows.rom" ] && [ ! -f "$STORAGE/$BOOT_MODE.rom" ]; then + mv -f "$STORAGE/windows.rom" "$STORAGE/$BOOT_MODE.rom" + fi + if [ -f "$STORAGE/windows.vars" ] && [ ! -f "$STORAGE/$BOOT_MODE.vars" ]; then + mv -f "$STORAGE/windows.vars" "$STORAGE/$BOOT_MODE.vars" + fi + fi + fi + + return 0 +} + +###################################### + +! parseVersion && exit 58 +! parseLanguage && exit 56 +! detectCustom && exit 59 + +if ! startInstall; then + bootWindows && return 0 + exit 68 +fi + +if [ ! -s "$ISO" ] || [ ! -f "$ISO" ]; then + echo "Error: ISO file not found or is empty." + exit 61 +fi + +if ! extractImage "$ISO" "$DIR" "$VERSION"; then + rm -f "$ISO" 2> /dev/null || true + exit 62 +fi + +if ! detectImage "$DIR" "$VERSION"; then + abortInstall "$DIR" "$ISO" && return 0 + exit 60 +fi + +if ! prepareImage "$ISO" "$DIR"; then + abortInstall "$DIR" "$ISO" && return 0 + exit 66 +fi + +if ! updateImage "$DIR" "$XML" "$LANGUAGE"; then + abortInstall "$DIR" "$ISO" && return 0 + exit 63 +fi + +if ! removeImage "$ISO"; then + exit 64 +fi + +if ! buildImage "$DIR"; then + exit 65 +fi + +if ! finishInstall "$BOOT" "N"; then + exit 69 +fi + +html "Successfully prepared image for installation..." +return 0 diff --git a/computer_use_demo/windowshost/vm/buildcontainer/power.sh b/computer_use_demo/windowshost/vm/buildcontainer/power.sh new file mode 100644 index 0000000..060f18c --- /dev/null +++ b/computer_use_demo/windowshost/vm/buildcontainer/power.sh @@ -0,0 +1,223 @@ +#!/usr/bin/env bash +set -Eeuo pipefail + +# Configure QEMU for graceful shutdown + +QEMU_TERM="" +QEMU_PORT=7100 +QEMU_TIMEOUT=110 +QEMU_DIR="/run/shm" +QEMU_PID="$QEMU_DIR/qemu.pid" +QEMU_PTY="$QEMU_DIR/qemu.pty" +QEMU_LOG="$QEMU_DIR/qemu.log" +QEMU_OUT="$QEMU_DIR/qemu.out" +QEMU_END="$QEMU_DIR/qemu.end" + +rm -f "$QEMU_DIR/qemu.*" +touch "$QEMU_LOG" + +_trap() { + func="$1" ; shift + for sig ; do + trap "$func $sig" "$sig" + done +} + +boot() { + + [ -f "$QEMU_END" ] && return 0 + + if [ -s "$QEMU_PTY" ]; then + if [ "$(stat -c%s "$QEMU_PTY")" -gt 7 ]; then + local fail="" + if [[ "${BOOT_MODE,,}" == "windows_legacy" ]]; then + grep -Fq "No bootable device." "$QEMU_PTY" && fail="y" + grep -Fq "BOOTMGR is missing" "$QEMU_PTY" && fail="y" + fi + if [ -z "$fail" ]; then + info "Windows has started successfully. You can directly view the VM at http://localhost:8006/vnc.html?view_only=1&autoconnect=1&resize=scale. Wait until setup is complete before interacting manually." + return 0 + fi + fi + fi + + error "Timeout while waiting for QEMU to boot the machine!" + + local pid + pid=$(<"$QEMU_PID") + { kill -15 "$pid" || true; } 2>/dev/null + + return 0 +} + +ready() { + + [ -f "$STORAGE/windows.boot" ] && return 0 + [ ! -s "$QEMU_PTY" ] && return 1 + + if [[ "${BOOT_MODE,,}" == "windows_legacy" ]]; then + local last + local bios="Booting from Hard" + last=$(grep "^Booting.*" "$QEMU_PTY" | tail -1) + [[ "${last,,}" != "${bios,,}"* ]] && return 1 + grep -Fq "No bootable device." "$QEMU_PTY" && return 1 + grep -Fq "BOOTMGR is missing" "$QEMU_PTY" && return 1 + return 0 + fi + + local line="\"Windows Boot Manager\"" + grep -Fq "$line" "$QEMU_PTY" && return 0 + + return 1 +} + +finish() { + + local pid + local reason=$1 + + touch "$QEMU_END" + + if [ -s "$QEMU_PID" ]; then + + pid=$(<"$QEMU_PID") + error "Forcefully terminating Windows, reason: $reason..." + { kill -15 "$pid" || true; } 2>/dev/null + + while isAlive "$pid"; do + sleep 1 + # Workaround for zombie pid + [ ! -s "$QEMU_PID" ] && break + done + fi + + if [ ! -f "$STORAGE/windows.boot" ] && [ -f "$BOOT" ]; then + # Remove CD-ROM ISO after install + if ready; then + touch "$STORAGE/windows.boot" + if [[ "$REMOVE" != [Nn]* ]]; then + rm -f "$BOOT" 2>/dev/null || true + fi + fi + fi + + pid="/var/run/tpm.pid" + [ -s "$pid" ] && pKill "$(<"$pid")" + + pid="/var/run/wsdd.pid" + [ -s "$pid" ] && pKill "$(<"$pid")" + + fKill "smbd" + + closeNetwork + + sleep 0.5 + echo "❯ Shutdown completed!" + + exit "$reason" +} + +terminal() { + + local dev="" + + if [ -s "$QEMU_OUT" ]; then + + local msg + msg=$(<"$QEMU_OUT") + + if [ -n "$msg" ]; then + + if [[ "${msg,,}" != "char"* || "$msg" != *"serial0)" ]]; then + echo "$msg" + fi + + dev="${msg#*/dev/p}" + dev="/dev/p${dev%% *}" + + fi + fi + + if [ ! -c "$dev" ]; then + dev=$(echo 'info chardev' | nc -q 1 -w 1 localhost "$QEMU_PORT" | tr -d '\000') + dev="${dev#*serial0}" + dev="${dev#*pty:}" + dev="${dev%%$'\n'*}" + dev="${dev%%$'\r'*}" + fi + + if [ ! -c "$dev" ]; then + error "Device '$dev' not found!" + finish 34 && return 34 + fi + + QEMU_TERM="$dev" + return 0 +} + +_graceful_shutdown() { + + local code=$? + + set +e + + if [ -f "$QEMU_END" ]; then + info "Received $1 while already shutting down..." + return + fi + + touch "$QEMU_END" + info "Received $1, sending ACPI shutdown signal..." + + if [ ! -s "$QEMU_PID" ]; then + error "QEMU PID file does not exist?" + finish "$code" && return "$code" + fi + + local pid="" + pid=$(<"$QEMU_PID") + + if ! isAlive "$pid"; then + error "QEMU process does not exist?" + finish "$code" && return "$code" + fi + + if ! ready; then + info "Cannot send ACPI signal during Windows setup, aborting..." + finish "$code" && return "$code" + fi + + # Send ACPI shutdown signal + echo 'system_powerdown' | nc -q 1 -w 1 localhost "${QEMU_PORT}" > /dev/null + + local cnt=0 + while [ "$cnt" -lt "$QEMU_TIMEOUT" ]; do + + sleep 1 + cnt=$((cnt+1)) + + ! isAlive "$pid" && break + # Workaround for zombie pid + [ ! -s "$QEMU_PID" ] && break + + info "Waiting for Windows to shutdown... ($cnt/$QEMU_TIMEOUT)" + + # Send ACPI shutdown signal + echo 'system_powerdown' | nc -q 1 -w 1 localhost "${QEMU_PORT}" > /dev/null + + done + + if [ "$cnt" -ge "$QEMU_TIMEOUT" ]; then + error "Shutdown timeout reached, aborting..." + fi + + finish "$code" && return "$code" +} + +SERIAL="pty" +MONITOR="telnet:localhost:$QEMU_PORT,server,nowait,nodelay" +MONITOR+=" -daemonize -D $QEMU_LOG -pidfile $QEMU_PID" + +_trap _graceful_shutdown SIGTERM SIGHUP SIGINT SIGABRT SIGQUIT + +return 0 diff --git a/computer_use_demo/windowshost/vm/buildcontainer/samba.sh b/computer_use_demo/windowshost/vm/buildcontainer/samba.sh new file mode 100644 index 0000000..07c535d --- /dev/null +++ b/computer_use_demo/windowshost/vm/buildcontainer/samba.sh @@ -0,0 +1,109 @@ +#!/usr/bin/env bash +set -Eeuo pipefail + +: "${SAMBA:="Y"}" + +[[ "$SAMBA" == [Nn]* ]] && return 0 +[[ "$NETWORK" == [Nn]* ]] && return 0 + +hostname="host.lan" +interface="dockerbridge" + +if [[ "$DHCP" == [Yy1]* ]]; then + hostname="$IP" + interface="$VM_NET_DEV" +fi + +addShare() { + local dir="$1" + local name="$2" + local comment="$3" + + mkdir -p "$dir" || return 1 + + if [ -z "$(ls -A "$dir")" ]; then + + chmod 777 "$dir" + + { echo "--------------------------------------------------------" + echo " $APP" + echo " For support visit $SUPPORT" + echo "--------------------------------------------------------" + echo "" + echo "Using this folder you can share files with the host machine." + echo "" + echo "To change its location, include the following bind mount in your compose file:" + echo "" + echo " volumes:" + echo " - \"/home/example:/${name,,}\"" + echo "" + echo "Or in your run command:" + echo "" + echo " -v \"/home/example:/${name,,}\"" + echo "" + echo "Replace the example path /home/example with the desired shared folder." + echo "" + } | unix2dos > "$dir/readme.txt" + + fi + + { echo "" + echo "[$name]" + echo " path = $dir" + echo " comment = $comment" + echo " writable = yes" + echo " guest ok = yes" + echo " guest only = yes" + echo " force user = root" + echo " force group = root" + } >> "/etc/samba/smb.conf" + + return 0 +} + +{ echo "[global]" + echo " server string = Dockur" + echo " netbios name = $hostname" + echo " workgroup = WORKGROUP" + echo " interfaces = $interface" + echo " bind interfaces only = yes" + echo " security = user" + echo " guest account = nobody" + echo " map to guest = Bad User" + echo " server min protocol = NT1" + echo "" + echo " # disable printing services" + echo " load printers = no" + echo " printing = bsd" + echo " printcap name = /dev/null" + echo " disable spoolss = yes" +} > "/etc/samba/smb.conf" + +share="/data" +[ ! -d "$share" ] && [ -d "$STORAGE/data" ] && share="$STORAGE/data" +[ ! -d "$share" ] && [ -d "/shared" ] && share="/shared" +[ ! -d "$share" ] && [ -d "$STORAGE/shared" ] && share="$STORAGE/shared" + +addShare "$share" "Data" "Shared" || error "Failed to create shared folder!" + +[ -d "/data2" ] && addShare "/data2" "Data2" "Shared" +[ -d "/data3" ] && addShare "/data3" "Data3" "Shared" + +if ! smbd; then + error "Samba daemon failed to start!" + smbd -i --debug-stdout || true +fi + +if [[ "${BOOT_MODE:-}" == "windows_legacy" ]]; then + # Enable NetBIOS on Windows 7 and lower + if ! nmbd; then + error "NetBIOS daemon failed to start!" + nmbd -i --debug-stdout || true + fi +else + # Enable Web Service Discovery on Vista and up + wsdd -i "$interface" -p -n "$hostname" & + echo "$!" > /var/run/wsdd.pid +fi + +return 0 diff --git a/computer_use_demo/windowshost/vm/win11def/win11x64-enterprise-eval.xml b/computer_use_demo/windowshost/vm/win11def/win11x64-enterprise-eval.xml new file mode 100644 index 0000000..36f13fb --- /dev/null +++ b/computer_use_demo/windowshost/vm/win11def/win11x64-enterprise-eval.xml @@ -0,0 +1,462 @@ +<?xml version="1.0" encoding="UTF-8"?> +<unattend xmlns="urn:schemas-microsoft-com:unattend" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State"> + <settings pass="windowsPE"> + <component name="Microsoft-Windows-International-Core-WinPE" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"> + <SetupUILanguage> + <UILanguage>en-US</UILanguage> + </SetupUILanguage> + <InputLocale>0409:00000409</InputLocale> + <SystemLocale>en-US</SystemLocale> + <UILanguage>en-US</UILanguage> + <UserLocale>en-US</UserLocale> + </component> + <component name="Microsoft-Windows-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"> + <DiskConfiguration> + <Disk wcm:action="add"> + <DiskID>0</DiskID> + <WillWipeDisk>true</WillWipeDisk> + <CreatePartitions> + <!-- System partition (ESP) --> + <CreatePartition wcm:action="add"> + <Order>1</Order> + <Type>EFI</Type> + <Size>128</Size> + </CreatePartition> + <!-- Microsoft reserved partition (MSR) --> + <CreatePartition wcm:action="add"> + <Order>2</Order> + <Type>MSR</Type> + <Size>128</Size> + </CreatePartition> + <!-- Windows partition --> + <CreatePartition wcm:action="add"> + <Order>3</Order> + <Type>Primary</Type> + <Extend>true</Extend> + </CreatePartition> + </CreatePartitions> + <ModifyPartitions> + <!-- System partition (ESP) --> + <ModifyPartition wcm:action="add"> + <Order>1</Order> + <PartitionID>1</PartitionID> + <Label>System</Label> + <Format>FAT32</Format> + </ModifyPartition> + <!-- MSR partition does not need to be modified --> + <ModifyPartition wcm:action="add"> + <Order>2</Order> + <PartitionID>2</PartitionID> + </ModifyPartition> + <!-- Windows partition --> + <ModifyPartition wcm:action="add"> + <Order>3</Order> + <PartitionID>3</PartitionID> + <Label>Windows</Label> + <Letter>C</Letter> + <Format>NTFS</Format> + </ModifyPartition> + </ModifyPartitions> + </Disk> + </DiskConfiguration> + <ImageInstall> + <OSImage> + <InstallTo> + <DiskID>0</DiskID> + <PartitionID>3</PartitionID> + </InstallTo> + <InstallToAvailablePartition>false</InstallToAvailablePartition> + </OSImage> + </ImageInstall> + <DynamicUpdate> + <Enable>true</Enable> + <WillShowUI>Never</WillShowUI> + </DynamicUpdate> + <UpgradeData> + <Upgrade>false</Upgrade> + <WillShowUI>Never</WillShowUI> + </UpgradeData> + <UserData> + <AcceptEula>true</AcceptEula> + <FullName>Docker</FullName> + <Organization>Windows for Docker</Organization> + </UserData> + <EnableFirewall>false</EnableFirewall> + <Diagnostics> + <OptIn>false</OptIn> + </Diagnostics> + <RunSynchronous> + <RunSynchronousCommand wcm:action="add"> + <Order>1</Order> + <Path>reg.exe add "HKLM\SYSTEM\Setup\LabConfig" /v BypassTPMCheck /t REG_DWORD /d 1 /f</Path> + </RunSynchronousCommand> + <RunSynchronousCommand wcm:action="add"> + <Order>2</Order> + <Path>reg.exe add "HKLM\SYSTEM\Setup\LabConfig" /v BypassSecureBootCheck /t REG_DWORD /d 1 /f</Path> + </RunSynchronousCommand> + <RunSynchronousCommand wcm:action="add"> + <Order>3</Order> + <Path>reg.exe add "HKLM\SYSTEM\Setup\LabConfig" /v BypassRAMCheck /t REG_DWORD /d 1 /f</Path> + </RunSynchronousCommand> + <RunSynchronousCommand wcm:action="add"> + <Order>4</Order> + <Path>reg.exe add "HKLM\SYSTEM\Setup\MoSetup" /v AllowUpgradesWithUnsupportedTPMOrCPU /t REG_DWORD /d 1 /f</Path> + </RunSynchronousCommand> + </RunSynchronous> + </component> + </settings> + <settings pass="offlineServicing"> + <component name="Microsoft-Windows-LUA-Settings" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"> + <EnableLUA>false</EnableLUA> + </component> + </settings> + <settings pass="generalize"> + <component name="Microsoft-Windows-PnPSysprep" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"> + <PersistAllDeviceInstalls>true</PersistAllDeviceInstalls> + </component> + <component name="Microsoft-Windows-Security-SPP" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"> + <SkipRearm>1</SkipRearm> + </component> + </settings> + <settings pass="specialize"> + <component name="Microsoft-Windows-Security-SPP-UX" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"> + <SkipAutoActivation>true</SkipAutoActivation> + </component> + <component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"> + <ComputerName>*</ComputerName> + <OEMInformation> + <Manufacturer>Dockur</Manufacturer> + <Model>Windows for Docker</Model> + <SupportHours>24/7</SupportHours> + <SupportPhone /> + <SupportProvider>Dockur</SupportProvider> + <SupportURL>https://github.com/dockur/windows/issues</SupportURL> + </OEMInformation> + <OEMName>Windows for Docker</OEMName> + </component> + <component name="Microsoft-Windows-ErrorReportingCore" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"> + <DisableWER>1</DisableWER> + </component> + <component name="Microsoft-Windows-IE-InternetExplorer" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"> + <DisableAccelerators>true</DisableAccelerators> + <DisableFirstRunWizard>true</DisableFirstRunWizard> + <Home_Page>https://google.com</Home_Page> + <Help_Page>about:blank</Help_Page> + </component> + <component name="Microsoft-Windows-IE-InternetExplorer" processorArchitecture="wow64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"> + <DisableAccelerators>true</DisableAccelerators> + <DisableFirstRunWizard>true</DisableFirstRunWizard> + <Home_Page>https://google.com</Home_Page> + <Help_Page>about:blank</Help_Page> + </component> + <component name="Microsoft-Windows-SQMApi" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"> + <CEIPEnabled>0</CEIPEnabled> + </component> + <component name="Microsoft-Windows-SystemRestore-Main" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"> + <DisableSR>1</DisableSR> + </component> + <component name="Microsoft-Windows-International-Core" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"> + <InputLocale>0409:00000409</InputLocale> + <SystemLocale>en-US</SystemLocale> + <UILanguage>en-US</UILanguage> + <UserLocale>en-US</UserLocale> + </component> + <component name="Microsoft-Windows-Deployment" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"> + <RunSynchronous> + <RunSynchronousCommand wcm:action="add"> + <Order>1</Order> + <Path>reg.exe add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\OOBE" /v BypassNRO /t REG_DWORD /d 1 /f</Path> + </RunSynchronousCommand> + <RunSynchronousCommand wcm:action="add"> + <Order>2</Order> + <Path>reg.exe load "HKU\mount" "C:\Users\Default\NTUSER.DAT"</Path> + </RunSynchronousCommand> + <RunSynchronousCommand wcm:action="add"> + <Order>3</Order> + <Path>reg.exe add "HKU\mount\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager" /v "ContentDeliveryAllowed" /t REG_DWORD /d 0 /f</Path> + </RunSynchronousCommand> + <RunSynchronousCommand wcm:action="add"> + <Order>4</Order> + <Path>reg.exe add "HKU\mount\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager" /v "FeatureManagementEnabled" /t REG_DWORD /d 0 /f</Path> + </RunSynchronousCommand> + <RunSynchronousCommand wcm:action="add"> + <Order>5</Order> + <Path>reg.exe add "HKU\mount\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager" /v "OEMPreInstalledAppsEnabled" /t REG_DWORD /d 0 /f</Path> + </RunSynchronousCommand> + <RunSynchronousCommand wcm:action="add"> + <Order>6</Order> + <Path>reg.exe add "HKU\mount\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager" /v "PreInstalledAppsEnabled" /t REG_DWORD /d 0 /f</Path> + </RunSynchronousCommand> + <RunSynchronousCommand wcm:action="add"> + <Order>7</Order> + <Path>reg.exe add "HKU\mount\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager" /v "PreInstalledAppsEverEnabled" /t REG_DWORD /d 0 /f</Path> + </RunSynchronousCommand> + <RunSynchronousCommand wcm:action="add"> + <Order>8</Order> + <Path>reg.exe add "HKU\mount\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager" /v "SilentInstalledAppsEnabled" /t REG_DWORD /d 0 /f</Path> + </RunSynchronousCommand> + <RunSynchronousCommand wcm:action="add"> + <Order>9</Order> + <Path>reg.exe add "HKU\mount\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager" /v "SoftLandingEnabled" /t REG_DWORD /d 0 /f</Path> + </RunSynchronousCommand> + <RunSynchronousCommand wcm:action="add"> + <Order>10</Order> + <Path>reg.exe add "HKU\mount\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager" /v "SubscribedContentEnabled" /t REG_DWORD /d 0 /f</Path> + </RunSynchronousCommand> + <RunSynchronousCommand wcm:action="add"> + <Order>11</Order> + <Path>reg.exe add "HKU\mount\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager" /v "SubscribedContent-310093Enabled" /t REG_DWORD /d 0 /f</Path> + </RunSynchronousCommand> + <RunSynchronousCommand wcm:action="add"> + <Order>12</Order> + <Path>reg.exe add "HKU\mount\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager" /v "SubscribedContent-338387Enabled" /t REG_DWORD /d 0 /f</Path> + </RunSynchronousCommand> + <RunSynchronousCommand wcm:action="add"> + <Order>13</Order> + <Path>reg.exe add "HKU\mount\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager" /v "SubscribedContent-338388Enabled" /t REG_DWORD /d 0 /f</Path> + </RunSynchronousCommand> + <RunSynchronousCommand wcm:action="add"> + <Order>14</Order> + <Path>reg.exe add "HKU\mount\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager" /v "SubscribedContent-338389Enabled" /t REG_DWORD /d 0 /f</Path> + </RunSynchronousCommand> + <RunSynchronousCommand wcm:action="add"> + <Order>15</Order> + <Path>reg.exe add "HKU\mount\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager" /v "SubscribedContent-338393Enabled" /t REG_DWORD /d 0 /f</Path> + </RunSynchronousCommand> + <RunSynchronousCommand wcm:action="add"> + <Order>16</Order> + <Path>reg.exe add "HKU\mount\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager" /v "SubscribedContent-353698Enabled" /t REG_DWORD /d 0 /f</Path> + </RunSynchronousCommand> + <RunSynchronousCommand wcm:action="add"> + <Order>17</Order> + <Path>reg.exe add "HKU\mount\Software\Microsoft\Windows\CurrentVersion\ContentDeliveryManager" /v "SystemPaneSuggestionsEnabled" /t REG_DWORD /d 0 /f</Path> + </RunSynchronousCommand> + <RunSynchronousCommand wcm:action="add"> + <Order>18</Order> + <Path>reg.exe add "HKU\mount\Software\Policies\Microsoft\Windows\CloudContent" /v "DisableCloudOptimizedContent" /t REG_DWORD /d 1 /f</Path> + </RunSynchronousCommand> + <RunSynchronousCommand wcm:action="add"> + <Order>19</Order> + <Path>reg.exe add "HKU\mount\Software\Policies\Microsoft\Windows\CloudContent" /v "DisableWindowsConsumerFeatures" /t REG_DWORD /d 1 /f</Path> + </RunSynchronousCommand> + <RunSynchronousCommand wcm:action="add"> + <Order>20</Order> + <Path>reg.exe add "HKU\mount\Software\Policies\Microsoft\Windows\CloudContent" /v "DisableConsumerAccountStateContent" /t REG_DWORD /d 1 /f</Path> + </RunSynchronousCommand> + <RunSynchronousCommand wcm:action="add"> + <Order>21</Order> + <Path>reg.exe unload "HKU\mount"</Path> + </RunSynchronousCommand> + <RunSynchronousCommand wcm:action="add"> + <Order>22</Order> + <Path>reg.exe add "HKLM\Software\Policies\Microsoft\Windows\CloudContent" /v "DisableCloudOptimizedContent" /t REG_DWORD /d 1 /f</Path> + </RunSynchronousCommand> + <RunSynchronousCommand wcm:action="add"> + <Order>23</Order> + <Path>reg.exe add "HKLM\Software\Policies\Microsoft\Windows\CloudContent" /v "DisableWindowsConsumerFeatures" /t REG_DWORD /d 1 /f</Path> + </RunSynchronousCommand> + <RunSynchronousCommand wcm:action="add"> + <Order>24</Order> + <Path>reg.exe add "HKLM\Software\Policies\Microsoft\Windows\CloudContent" /v "DisableConsumerAccountStateContent" /t REG_DWORD /d 1 /f</Path> + </RunSynchronousCommand> + <RunSynchronousCommand wcm:action="add"> + <Order>25</Order> + <Path>reg.exe add "HKLM\SOFTWARE\Policies\Microsoft\Windows NT\CurrentVersion\NetworkList\Signatures\FirstNetwork" /v Category /t REG_DWORD /d 1 /f</Path> + <Description>Set Network Location to Home</Description> + </RunSynchronousCommand> + </RunSynchronous> + </component> + <component name="Microsoft-Windows-TerminalServices-LocalSessionManager" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"> + <fDenyTSConnections>false</fDenyTSConnections> + </component> + <component name="Microsoft-Windows-TerminalServices-RDP-WinStationExtensions" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"> + <UserAuthentication>0</UserAuthentication> + </component> + <component name="Networking-MPSSVC-Svc" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"> + <FirewallGroups> + <FirewallGroup wcm:action="add" wcm:keyValue="RemoteDesktop"> + <Active>true</Active> + <Profile>all</Profile> + <Group>@FirewallAPI.dll,-28752</Group> + </FirewallGroup> + </FirewallGroups> + </component> + </settings> + <settings pass="auditSystem" /> + <settings pass="auditUser" /> + <settings pass="oobeSystem"> + <component name="Microsoft-Windows-SecureStartup-FilterDriver" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"> + <PreventDeviceEncryption>true</PreventDeviceEncryption> + </component> + <component name="Microsoft-Windows-EnhancedStorage-Adm" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"> + <TCGSecurityActivationDisabled>1</TCGSecurityActivationDisabled> + </component> + <component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"> + <UserAccounts> + <LocalAccounts> + <LocalAccount wcm:action="add"> + <Name>Docker</Name> + <Group>Administrators</Group> + <Password> + <Value /> + <PlainText>true</PlainText> + </Password> + </LocalAccount> + </LocalAccounts> + <AdministratorPassword> + <Value>password</Value> + <PlainText>true</PlainText> + </AdministratorPassword> + </UserAccounts> + <AutoLogon> + <Username>Docker</Username> + <Enabled>true</Enabled> + <LogonCount>65432</LogonCount> + <Password> + <Value /> + <PlainText>true</PlainText> + </Password> + </AutoLogon> + <Display> + <ColorDepth>32</ColorDepth> + <HorizontalResolution>1920</HorizontalResolution> + <VerticalResolution>1080</VerticalResolution> + </Display> + <OOBE> + <HideEULAPage>true</HideEULAPage> + <HideLocalAccountScreen>true</HideLocalAccountScreen> + <HideOEMRegistrationScreen>true</HideOEMRegistrationScreen> + <HideOnlineAccountScreens>true</HideOnlineAccountScreens> + <HideWirelessSetupInOOBE>true</HideWirelessSetupInOOBE> + <NetworkLocation>Home</NetworkLocation> + <ProtectYourPC>3</ProtectYourPC> + <SkipUserOOBE>true</SkipUserOOBE> + <SkipMachineOOBE>true</SkipMachineOOBE> + </OOBE> + <RegisteredOrganization>Dockur</RegisteredOrganization> + <RegisteredOwner>Windows for Docker</RegisteredOwner> + <FirstLogonCommands> + <SynchronousCommand wcm:action="add"> + <Order>1</Order> + <CommandLine>reg.exe add "HKLM\SYSTEM\CurrentControlSet\Services\LanmanWorkstation\Parameters" /v "AllowInsecureGuestAuth" /t REG_DWORD /d 1 /f</CommandLine> + <Description>Allow guest access to network shares</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>2</Order> + <CommandLine>reg.exe add "HKLM\SYSTEM\CurrentControlSet\Services\LanmanWorkstation\Parameters" /v "RequireSecuritySignature" /t REG_DWORD /d 0 /f</CommandLine> + <Description>Disable SMB signing requirement</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>3</Order> + <CommandLine>reg.exe add "HKLM\SYSTEM\CurrentControlSet\Control\Lsa" /v LimitBlankPasswordUse /t REG_DWORD /d 0 /f</CommandLine> + <Description>Allow RDP login with blank password</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>4</Order> + <CommandLine>reg.exe add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\PasswordLess\Device" /v "DevicePasswordLessBuildVersion" /t REG_DWORD /d 0 /f</CommandLine> + <Description>Enable option for passwordless sign-in</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>5</Order> + <CommandLine>cmd /C wmic useraccount where name="Docker" set PasswordExpires=false</CommandLine> + <Description>Password Never Expires</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>6</Order> + <CommandLine>cmd /C POWERCFG -H OFF</CommandLine> + <Description>Disable Hibernation</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>7</Order> + <CommandLine>cmd /C POWERCFG -X -monitor-timeout-ac 0</CommandLine> + <Description>Disable monitor blanking</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>8</Order> + <CommandLine>reg.exe add "HKLM\SOFTWARE\Policies\Microsoft\Edge" /v "HideFirstRunExperience" /t REG_DWORD /d 1 /f</CommandLine> + <Description>Disable first-run experience in Edge</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>9</Order> + <CommandLine>reg.exe add "HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Advanced" /v "HideFileExt" /t REG_DWORD /d 0 /f</CommandLine> + <Description>Show file extensions in Explorer</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>10</Order> + <CommandLine>reg.exe add "HKLM\SYSTEM\CurrentControlSet\Control\Power" /v "HibernateFileSizePercent" /t REG_DWORD /d 0 /f</CommandLine> + <Description>Zero Hibernation File</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>11</Order> + <CommandLine>reg.exe add "HKLM\SYSTEM\CurrentControlSet\Control\Power" /v "HibernateEnabled" /t REG_DWORD /d 0 /f</CommandLine> + <Description>Disable Hibernation</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>12</Order> + <CommandLine>cmd /C POWERCFG -X -standby-timeout-ac 0</CommandLine> + <Description>Disable Sleep</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>13</Order> + <CommandLine>reg.exe add "HKLM\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services" /v "fAllowUnlistedRemotePrograms" /t REG_DWORD /d 1 /f</CommandLine> + <Description>Enable RemoteAPP to launch unlisted programs</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>14</Order> + <CommandLine>reg.exe add "HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Advanced" /v "ShowTaskViewButton" /t REG_DWORD /d 0 /f</CommandLine> + <Description>Remove Task View from the Taskbar</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>15</Order> + <CommandLine>reg.exe add "HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Advanced" /v "TaskbarDa" /t REG_DWORD /d 0 /f</CommandLine> + <Description>Remove Widgets from the Taskbar</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>16</Order> + <CommandLine>reg.exe add "HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Advanced" /v "TaskbarMn" /t REG_DWORD /d 0 /f</CommandLine> + <Description>Remove Chat from the Taskbar</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>17</Order> + <CommandLine>reg.exe add "HKLM\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" /v "NoAutoUpdate" /t REG_DWORD /d 1 /f</CommandLine> + <Description>Turn off Windows Update auto download</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>18</Order> + <CommandLine>netsh advfirewall firewall set rule group="@FirewallAPI.dll,-32752" new enable=Yes</CommandLine> + <Description>Enable Network Discovery</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>19</Order> + <CommandLine>netsh advfirewall firewall set rule group="@FirewallAPI.dll,-28502" new enable=Yes</CommandLine> + <Description>Enable File Sharing</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>20</Order> + <CommandLine>reg.exe add "HKCU\Control Panel\UnsupportedHardwareNotificationCache" /v SV1 /d 0 /t REG_DWORD /f</CommandLine> + <Description>Disable unsupported hardware notifications</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>21</Order> + <CommandLine>reg.exe add "HKCU\Control Panel\UnsupportedHardwareNotificationCache" /v SV2 /d 0 /t REG_DWORD /f</CommandLine> + <Description>Disable unsupported hardware notifications</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>22</Order> + <CommandLine>pnputil -i -a C:\Windows\Drivers\viogpudo\viogpudo.inf</CommandLine> + <Description>Install VirtIO display driver</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>23</Order> + <CommandLine>cmd /C rd /q C:\Windows.old</CommandLine> + <Description>Remove empty Windows.old folder</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <Order>24</Order> + <CommandLine>cmd /C if exist "C:\OEM\install.bat" start "Install" "cmd /C C:\OEM\install.bat"</CommandLine> + <Description>Execute custom script from the OEM folder if exists</Description> + </SynchronousCommand> + </FirstLogonCommands> + </component> + </settings> +</unattend> \ No newline at end of file diff --git a/computer_use_demo/windowshost/vm/win11iso/README.md b/computer_use_demo/windowshost/vm/win11iso/README.md new file mode 100644 index 0000000..23c4741 --- /dev/null +++ b/computer_use_demo/windowshost/vm/win11iso/README.md @@ -0,0 +1 @@ +Add your Win11E setup.iso to this folder \ No newline at end of file diff --git a/computer_use_demo/windowshost/vm/win11setup/firstboot/install.bat b/computer_use_demo/windowshost/vm/win11setup/firstboot/install.bat new file mode 100644 index 0000000..08324f2 --- /dev/null +++ b/computer_use_demo/windowshost/vm/win11setup/firstboot/install.bat @@ -0,0 +1,31 @@ +@echo off + +SET ScriptFolder=\\host.lan\Data +SET LogFile=%ScriptFolder%\firstboot_log.txt + +echo Running PowerShell script... > %LogFile% + +:: Check for PowerShell availability +where powershell >> %LogFile% 2>&1 +if %ERRORLEVEL% neq 0 ( + echo PowerShell is not available! >> %LogFile% + echo PowerShell is not available! + exit /b 1 +) + +:: Add a 30-second delay +echo Waiting for 30 seconds before continuing... >> %LogFile% +timeout /t 30 /nobreak >> %LogFile% 2>&1 + +:: Run PowerShell script with ExecutionPolicy Bypass and log errors +echo Running setup.ps1... >> %LogFile% + +powershell -ExecutionPolicy Bypass -File "%ScriptFolder%\setup.ps1" >> %LogFile% 2>&1 + +if %ERRORLEVEL% neq 0 ( + echo An error occurred. See %LogFile% for details. +) else ( + echo PowerShell script has completed successfully. +) + +echo PowerShell script has completed. \ No newline at end of file diff --git a/computer_use_demo/windowshost/vm/win11setup/setupscripts/on-logon.ps1 b/computer_use_demo/windowshost/vm/win11setup/setupscripts/on-logon.ps1 new file mode 100644 index 0000000..3221ac2 --- /dev/null +++ b/computer_use_demo/windowshost/vm/win11setup/setupscripts/on-logon.ps1 @@ -0,0 +1,7 @@ +$scriptFolder = "\\host.lan\Data" +$pythonScriptFile = "$scriptFolder\server\main.py" +$pythonServerPort = 5000 + +# Start the flask computer use server +Write-Host "Running the server on port $pythonServerPort" +python $pythonScriptFile --port $pythonServerPort diff --git a/computer_use_demo/windowshost/vm/win11setup/setupscripts/server/cursor.png b/computer_use_demo/windowshost/vm/win11setup/setupscripts/server/cursor.png new file mode 100644 index 0000000000000000000000000000000000000000..d3a3c5bbe1d070b65dc3bb31ab18998823ff54f8 GIT binary patch literal 3207 zcmV;240!X2P)<h;3K|Lk000e1NJLTq000yK001Be1^@s65@J}k000T0X+uL$Nkc;* zP;zf(X>4Tx0C=43nP*T`Sr&lrd#`h#p}T1^G&xC>(17HeBu7z!?k3Yhlfj4#j=%^a zB1sWM6mY=NA;>5yq8Jbb1<cdPAfSwcqlk*i0{)n-+O6H%s;&LAeSW<6)w!qMIrr7A zdSBH8K!%XOPE3LI0Z8Pf@_n7{n86{TOzeGt17tt~RDg+KrzCm$Ir)R-Sg8g75bs9; zv~s_v^Uv4+Wso?DJPrU5eVOAp+!XdQ?_A~>P9kR|KfS^ksY$5-z~0MD=PwVq%$yaD z{KlCp9Q}=pR%`ry?U(r|060?gDicnvaO5geyH=PT!%qZ2x^KB&g`LD-e!lcdFU4;& ztcK{yewfi*9+w}H%H=Ts!>#@M_`6={l;xNIUvKa~?z3`szR_FD)iVJxD*noCDFcA@ z8UUo^ubj$Z04Tcw=;-+?7kM0j&JqA5pRv>U>ECjw)!6|7PyiiB0ePSdG=L5;1g5|O z*Z@c12E2ej2mzY_8^nM_zy}#%E7%3{K_Mss2SF961r4AHw1P9B6PyQ^!8LFT+yx`x zF%W=B@CwX<_h1o%AS^_NXpj`70I5RSkRil^tRP3o1M-DJpl~P};z4Q9R%ka=2$e!r zP#x3+{REwZE<-n<A?P7A4$VM+LZ4s+Cc$)A4pxKpU>0l(yTktQMmQQyhO^;3cpqE= z*Tbja4)`*B3m$<5@GE#80SFOcAc}|%!b0p3FC-L+LiosbqyQ;L>XBBY8|g>xAp&F; z`G~@xXecI12W5eBK?R`LsASZ3R1vBQbrRKq>O<W_jicsJU(sZ=ELt0Fj&?-{qodIo z=zMfJx)I%uzKR|}Pom#rFc=0#9b<}d!GvJqFgcha%wbF$<`QNIGl7}MVzE+KZLB5M z3mbt=#pYuxv8S*XvBTI&><1hXr+_oWIpIQaJX|iW9M_EN!42c4aEo|Ryei%t?}g{! zv+%|E27EXE4t^59NDw2a6D$e-ggC-3LIt6faFy_c@Rmp<DiO_yKEzn!PGSY|H1Rrd zocNJMC25iDNnxZk(mv7&(k0SE(pxf_tVXsWhmupt`^Zh?E97V74<a-XT@hCij>ry? zYLO0+VUgDqJVljaOW8!prj%38QtnV@Me(9)qV}Q@qT5BQMbC+jiO!4B#0<o|#1h4d z#G1u!h)q+mR5hw2l|#*=9;05NPS6mVGR>aGrtPLR(5})Z=@_~?-IX3kFQorSAEeKT zQ^k$M1I07NtHm#f3m6DPjp52jU=%aXFh&`lB;+J)CAboMC0ZqhB^D%QByA+Qk_D1& zk|UCzq?l5UQt?vzr8=daN~5H;q<y3_q>o5nm7bNM%UH;;WeQ}@$UKsTWwm5|WpiYY z$qvZQ%gM_*%O%NG%3YD0m1oG?$j8Z-$@j?5D9{wF6k-+16fP>fWQsFwnF-7aW*>7- zQC87KF-@^faZqtlNnI&GDOagY>8Ub7nWY@1T&CQsJg1_d;;FJ#rA6hDDqfYP8l!qh zwO@5XO<gTWZI4=~+O)c)x~qD&dW-rK4YG!{MzTh|#(hnUCQCD3^RVV!Em+G~D@LnY z>y9?8ZK55keOP;F4SJ2)8s3`vHDfv?9b27rofe&OU52iQZmw>p?rS|2y->aVdi{D| z^^Nrt^c(aa8&D103~~*+4gNILGGrT88{S__UhA}W$J(y7Z;Z5!xJE~e9vIV%J&pGm z_ZojOF*V_vw3^JAs+fkG)|ig5=qztm5$n1c+RV;uhuL|vkLD)kspe<Q|FF=ph_`6A zm|my8j=Qd5-Grr*WrSs&rND}56>fFZN?@&M9bsK>{oF>`hGWxcGi9r38)tjU_O+e9 z9pA3qZo%H%ew+Oz2gJeIp}=9#k?I)WSmpT4Ny#bNsl{o|+1NS9`H~CT#ocAU%b2U2 zE8Dfnb<WMyZJS%4JIUS8y~_Q$hn5H5quUeqbn`sm`DDH7dfxg@FW}|sb-?SXx4L(- z_b)yeA8(&(pBKIczFU3!{b+uh{hIyW``h^!`#%oQ2uKU)4WtBy1vUjP1UUqi1_^@o zg0}_V3Xu+p4e1KSh6aW<hR$zr+;DKiWSDVSe%R<n^^KVuZ)}p@l(6Z-X3FM>&1b{W z;X&ce;h!VCBI+aNBV8kFBInrl>_hBV92-s<XNGIZE#*!{Sw@vcO-EZsABdibv5qN^ znT@rNt%`jU=Mq;Nw-E0Ye<FS<At<3O5tkUAc#cQq#q;{M$ZyHqGL)p9l%FI>Hcvj7 zJjZwEH>N--8&kSc>B}GKfi#V@@6yK8t<tO0KV$@EoXr%=<Yf+IX=Uxrn#y*{KAwZh zVdwO2RouF3>-aX?ZFSqB?UCDicPQ`3+cCM*dFRPpgkABwZs+Rfmgc_O9kjbUPcCm~ z-o$s#-?ik6<n!~#_E_wx{T}sw?DvCv*Y2&{`?Y{maI;Xqu)Oed5xeN-4+cL}{IIky zYTsb7N%4^qObM@Kbieiflci#%*`*T)JP&k~G0O_d-W}Y0@J6{&dF>&>p|nGS3Xh7e zN|nlz%Fk6XRU_4Q)onGhHG69o4s#9<AF(;oS}RvuSo`T{?9m5x&UGF2YW3yEFvrr5 zy=Vw*=s(Un-qa}FSlIaG1n-34r0>bTCex;-X4&TA7Puv?W#&}ask=Wq{Mgm1(^}WY zXe&GoPN$uI`BV5$qi5XD^qys%ZT(sG=bCm}dqD@(k<~HR8Ph4~3h5d;=XS2Q+oHSu z7u{b@oL4+weL?&}aSx#<uV?9E_Qm;2Ntb3XM_-=k-Q4^5O7NBYeLj7ISKY5(zvgu9 zO22LY#p{;W&)+b=aqcGTX6G%FTO9+&1MRnsZ?_Mc40hZxz0-Bq>~8nax}lz7>*34y z9PVAa?|T2%$oi3?(SXqhV_{>02b>4f4-+1~d6fQW@$t?l=qCkFMW2>GlYLhItLCq5 z0u#Z7afk7N=YG#0Pp~Im{g(3E=gGVm<QL^r3R6wfhSTR~oM!I6-0*VpRnn`)+5F$d zey@40@%raKZ2lOS3!a;N!+*2%XVF`!w<q2iz3ZL#o`3c}@%`dL!3U`iCqJ@2_J0cc zG_{!V8T+~7i{_W^ukK%;EF~^230*+w0zwxMx`5CHgf1X-0ig>BT|nppLKhIafY1em zE+BM)|6><e{g1O8$jar|7y$k`0C?O6pt1@8wI~26VX55A)Rk4<7AAX3QWifpIwo}u zGcAS7jN)^-30Z*fHx7Ra9bWTRr2qf|p-DtRRCt`N)lW#%VHm*iZ|{tmE`EZV`Datq zAr1#JtUTnQd2t702MrIQK_2R$msF5C6<rKMC?h&dy2$oZ`@=Muu!A@h4<b=s@(^2i zF%a}%!E#TRjHa6#?e`qt=Y2l!^Zxj~?+b7bBg1O7{<2ss4-5l`hK9uF^NHDPeq;zZ zI5;RGB0ir_3JVLL0B3Rn2L=Y@D5j>SMANiqfXNV0L}X@WMv9AzUjb(g0YyZD!Jw3t zmAwYchJYd>i;Ig=Sy|}^3Jd{7M3$D8q`JEL9dO<dP();Pbye!?>puWRhJYd>>+9>% z*w`2bN-}vWn?+qN7rL&yolfUl;9|}oKvPo_)6>&eTUuJ)164VL01XWd1OkD}t*xy= zpeAP!ptiOaUDuo2+S=v;d(I$$)oLXW2sqqs_dMXp83ZUVFURlq+dUr7JkXpo2vAyD z%IxfHO;1lx5V!=8mu>O#@-lmSdnd+CCKH{VomDF<E1_5{b~9^mZf;J-#>O53pHk(O zQhC5(RwqS7BpQuMMMcHSObe&{h8r6jL?V%-F`LasS6A0fQv51OalFIr?QL;593MR% zPb`s0B;__YH>Iek=xNsA{{FsnbaZ?LYy|}cqibtxC$`?#*Y_Q$$Oy*cap~>t-36}v zNm={*`*%}aXZiX0>7U)BS8sTD_$P4v-&{#a$&0P6tt2p!NQl?#jgszu92psriHQjT z?xaXNM@L709Pe^rVL_BqcT<9PyInL*dz@|ub#--bc6N4>z<4|^?d|QKfpe*9o^$#; zd~JMu{P1{}x~_{->Q<_POy}9;a=9W02M2$#a5ya8-QC}mQnxeIri~kulamq(g~aRi teo;yd0~h`m1F&eCwyc!84-}s^@iz_-B{HTjtFHh6002ovPDHLkV1i9}Eqwq0 literal 0 HcmV?d00001 diff --git a/computer_use_demo/windowshost/vm/win11setup/setupscripts/server/main.py b/computer_use_demo/windowshost/vm/win11setup/setupscripts/server/main.py new file mode 100644 index 0000000..5343918 --- /dev/null +++ b/computer_use_demo/windowshost/vm/win11setup/setupscripts/server/main.py @@ -0,0 +1,81 @@ +import os +import logging +import argparse +import shlex +import subprocess +from flask import Flask, request, jsonify, send_file +import threading +import traceback +import pyautogui +from PIL import Image +from io import BytesIO + +parser = argparse.ArgumentParser() +parser.add_argument("--log_file", help="log file path", type=str, + default=os.path.join(os.path.dirname(__file__), "server.log")) +parser.add_argument("--port", help="port", type=int, default=5000) +args = parser.parse_args() + +logging.basicConfig(filename=args.log_file,level=logging.DEBUG, filemode='w' ) +logger = logging.getLogger('werkzeug') + +app = Flask(__name__) + +computer_control_lock = threading.Lock() + +@app.route('/probe', methods=['GET']) +def probe_endpoint(): + return jsonify({"status": "Probe successful", "message": "Service is operational"}), 200 + +@app.route('/execute', methods=['POST']) +def execute_command(): + # Only execute one command at a time + with computer_control_lock: + data = request.json + # The 'command' key in the JSON request should contain the command to be executed. + shell = data.get('shell', False) + command = data.get('command', "" if shell else []) + + if isinstance(command, str) and not shell: + command = shlex.split(command) + + # Expand user directory + for i, arg in enumerate(command): + if arg.startswith("~/"): + command[i] = os.path.expanduser(arg) + + # Execute the command without any safety checks. + try: + result = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=shell, text=True, timeout=120) + return jsonify({ + 'status': 'success', + 'output': result.stdout, + 'error': result.stderr, + 'returncode': result.returncode + }) + except Exception as e: + logger.error("\n" + traceback.format_exc() + "\n") + return jsonify({ + 'status': 'error', + 'message': str(e) + }), 500 + +@app.route('/screenshot', methods=['GET']) +def capture_screen_with_cursor(): + cursor_path = os.path.join(os.path.dirname(__file__), "cursor.png") + screenshot = pyautogui.screenshot() + cursor_x, cursor_y = pyautogui.position() + cursor = Image.open(cursor_path) + # make the cursor smaller + cursor = cursor.resize((int(cursor.width / 1.5), int(cursor.height / 1.5))) + screenshot.paste(cursor, (cursor_x, cursor_y), cursor) + + + # Convert PIL Image to bytes and send + img_io = BytesIO() + screenshot.save(img_io, 'PNG') + img_io.seek(0) + return send_file(img_io, mimetype='image/png') + +if __name__ == '__main__': + app.run(debug=True, host="0.0.0.0", port=args.port) \ No newline at end of file diff --git a/computer_use_demo/windowshost/vm/win11setup/setupscripts/server/requirements.txt b/computer_use_demo/windowshost/vm/win11setup/setupscripts/server/requirements.txt new file mode 100644 index 0000000..31b005f --- /dev/null +++ b/computer_use_demo/windowshost/vm/win11setup/setupscripts/server/requirements.txt @@ -0,0 +1,2 @@ +flask +PyAutoGUI \ No newline at end of file diff --git a/computer_use_demo/windowshost/vm/win11setup/setupscripts/setup-tools.psm1 b/computer_use_demo/windowshost/vm/win11setup/setupscripts/setup-tools.psm1 new file mode 100644 index 0000000..063935d --- /dev/null +++ b/computer_use_demo/windowshost/vm/win11setup/setupscripts/setup-tools.psm1 @@ -0,0 +1,197 @@ +function Get-Tools { + param( + [string]$toolsConfigJson + ) + + # Convert the JSON string to a PowerShell object + $toolsList = $toolsConfigJson | ConvertFrom-Json + + return $toolsList +} + +function Get-ToolDetails { + param( + $toolsList, + [string]$toolName + ) + + # Check if the program exists in the JSON data + if ($toolsList.PSObject.Properties.Name -contains $toolName) { + # Return the program details as a PowerShell object + return $toolsList.$toolName + } else { + # Handle the case where the program is not found + Write-Host "Program '$toolName' not found in the list." + return $null + } +} + +function Invoke-DownloadFileFromAvailableMirrors { + param ( + [string[]]$mirrorUrls, + [string]$outfile + ) + foreach ($url in $mirrorUrls) { + try { + $result = Invoke-DownloadFile -url $url -outfile $outfile + if ($result -eq $true) { + Write-Host "Downloaded using $url" + return $true + } + } catch { + Write-Host "Error downloading from $url. Please check and update the mirrors." + } + } + + Write-Host "Downloading from the provided mirrors failed. Please check and update the mirrors." + return $false +} + +function Invoke-DownloadFile { + param ( + [string]$url, + [string]$outfile + ) + # Makes download faster by disabling progress bar + $ProgressPreference = "SilentlyContinue" + + $retryCount = 0 + $maxRetries = 3 + $sleepSeconds = 2 + $maxSleepSeconds = 10 + $userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36" + + # Ensure directory exists + $directory = Split-Path -Path $outfile -Parent + if (-Not (Test-Path -Path $directory)) { + Write-Host "Creating directory $directory..." + New-Item -Path $directory -ItemType Directory -Force | Out-Null + } + + while ($retryCount -lt $maxRetries) { + try { + Invoke-RestMethod -Uri $url -OutFile $outfile -Headers @{"User-Agent" = $userAgent} + Write-Host "Download successful, file saved to: $outfile" + break + } catch { + $retryCount++ + Write-Host "Attempt $retryCount of $maxRetries failed. Error: $($_.Exception.Message)" + Start-Sleep -Seconds $sleepSeconds + $sleepSeconds = [Math]::Min($sleepSeconds * 2, $maxSleepSeconds) # Exponential backoff with a cap + } + } + + if ($retryCount -eq $maxRetries) { + Write-Host "Failed to download the file after $maxRetries attempts." + return $false + } + + return $true +} + +function Add-ToEnvPath { + param ( + [string]$NewPath + ) + + # Get the current PATH environment variable + $envPath = [Environment]::GetEnvironmentVariable("PATH", "Machine") + + # Append the new path to the existing PATH + $newPath = "$envPath;$NewPath" + + # Set the updated PATH environment variable + [Environment]::SetEnvironmentVariable("PATH", $newPath, "Machine") + + # Fetch updates from the shell + $env:PATH += ";${newPath}" +} + +function Register-LogonTask { + param( + + [parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, HelpMessage = "Name of the scheduled task")] + [string] + $TaskName, + + [parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, HelpMessage = "Path to the .py script")] + [string] + $ScriptPath, + + [parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true, HelpMessage = "Arguments to the .py script")] + [string] + $Arguments = "", + + [parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true, HelpMessage = "Local Account username")] + [string] + $LocalUser, + + [parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true, HelpMessage = "Local Account password")] + [string] + $LocalPassword, + + [parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true, HelpMessage = "Whether to execute the command as SYSTEM")] + [switch] + $AsSystem = $false, + + [parameter(Mandatory = $false, ValueFromPipelineByPropertyName = $true, HelpMessage = "logging file")] + [string] + $LogFilePath + ) + + $scriptDirectory = Split-Path $ScriptPath + + $taskActionArgument = "-ExecutionPolicy Bypass -windowstyle hidden -Command `"try { . '$ScriptPath' $Arguments } catch { Write `$_.Exception.Message | Out-File $($TaskName)_Log.txt } finally { } `"" + $taskAction = New-ScheduledTaskAction -Execute "$PSHome\powershell.exe" -Argument $taskActionArgument -WorkingDirectory $scriptDirectory + + $params = @{ + Force = $True + Action = $taskAction + RunLevel = "Highest" + TaskName = $TaskName + } + + $taskTrigger = New-ScheduledTaskTrigger -AtLogOn + $params.Add("Trigger", $taskTrigger) + + if ($AsSystem) { + $params.Add("User", "NT AUTHORITY\SYSTEM") + } + else { + $params.Add("User", $LocalUser) + if ($LocalPassword) { + $params.Add("Password", $LocalPassword) + } + } + + Write-Host "Registering scheduled task '$TaskName' to run 'powershell.exe $taskActionArgument'..." + Register-ScheduledTask @params +} + +# Function to attempt pip install and handle failures +function Install-PythonPackages { + param ( + [string]$Package = "", + [string]$Arguments = "", + [string]$RequirementsPath = "" + ) + $RetryCount = 3 + $currentAttempt = 0 + while ($currentAttempt -lt $RetryCount) { + if (-not [string]::IsNullOrWhiteSpace($RequirementsPath)) { + & python -m pip install --no-cache-dir -r $RequirementsPath $Arguments + } else { + & python -m pip install --no-cache-dir $Package $Arguments + } + if ($LASTEXITCODE -eq 0) { + Write-Host "Installation successful." + return + } else { + Write-Host "Attempt $($currentAttempt + 1) failed. Retrying..." + Start-Sleep -Seconds 10 + $currentAttempt++ + } + } + Write-Error "Failed to install after $RetryCount attempts." + exit +} \ No newline at end of file diff --git a/computer_use_demo/windowshost/vm/win11setup/setupscripts/setup.ps1 b/computer_use_demo/windowshost/vm/win11setup/setupscripts/setup.ps1 new file mode 100644 index 0000000..013cc2f --- /dev/null +++ b/computer_use_demo/windowshost/vm/win11setup/setupscripts/setup.ps1 @@ -0,0 +1,392 @@ +$ErrorActionPreference = "Continue" # until downloading from mirrors is more stable + +# Section - General Setup +$scriptFolder = "\\host.lan\Data" +$toolsFolder = "C:\Users\$env:USERNAME\Tools" + +# Load the shared setup-tools module +Import-Module (Join-Path $scriptFolder -ChildPath "setup-tools.psm1") + +# Check if profile exists +if (-not (Test-Path $PROFILE)) { + New-Item -ItemType File -Path $PROFILE -Force +} + +# Create a folder where we store all the standalone executables +if (-not (Test-Path $toolsFolder)) { + New-Item -ItemType Directory -Path $toolsFolder -Force + $envPath = [Environment]::GetEnvironmentVariable("PATH", "Machine") + $newPath = "$envPath;$toolsFolder" + [Environment]::SetEnvironmentVariable("PATH", $newPath, "Machine") +} + +# Section - Tools Installation + +# Set TLS version to 1.2 or higher +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 -bor [Net.SecurityProtocolType]::Tls13 + +# Load the tools config json listing mirrors and aliases used for installing tools +$toolsConfigJsonPath = Join-Path $scriptFolder -ChildPath "tools_config.json" +$toolsConfigJson = Get-Content -Path $toolsConfigJsonPath -Raw +$toolsList = Get-Tools -toolsConfigJson $toolsConfigJson + +## - Python +$pythonToolName = "Python" +$userPythonPath = "$env:LOCALAPPDATA\Programs\Python" +$pythonDetails = Get-ToolDetails -toolsList $toolsList -toolName $pythonToolName +$pythonAlias = $pythonDetails.alias + +# Check for Python installation +$pythonExecutablePath = Get-ChildItem -Path $userPythonPath -Filter python.exe -Recurse -ErrorAction SilentlyContinue | Select-Object -First 1 -ExpandProperty FullName + +# Force to install Python 3.10 as the pre-installed version on Windows may not work sometimes +Write-Host "Downloading Python $pythonVersion..." +$pythonInstallerFilePath = "$env:TEMP\python_installer.exe" +$downloadResult = Invoke-DownloadFileFromAvailableMirrors -mirrorUrls $pythonDetails.mirrors -outfile $pythonInstallerFilePath +if (-not $downloadResult) { + Write-Host "Failed to download Python. Please try again later or install manually." +} else { + Write-Host "Installing Python for current user..." + Start-Process -FilePath $pythonInstallerFilePath -Args "/quiet InstallAllUsers=0 PrependPath=0" -NoNewWindow -Wait + $pythonExecutablePath = "$userPythonPath\Python310\python.exe" + $setAliasExpression = "Set-Alias -Name $pythonAlias -Value `"$pythonExecutablePath`"" + Add-Content -Path $PROFILE -Value $setAliasExpression + Invoke-Expression $setAliasExpression +} + +## - Git +$gitToolName = "git" +$gitToolDetails = Get-ToolDetails -toolsList $toolsList -toolName $gitToolName + +# Check for Git installation +try { + git --version | Out-Null + Write-Host "Git is already installed." +} catch { + Write-Host "Git is not installed. Downloading and installing Git..." + $gitInstallerFilePath = "$env:TEMP\git_installer.exe" + $downloadResult = Invoke-DownloadFileFromAvailableMirrors -mirrorUrls $gitToolDetails.mirrors -outfile $gitInstallerFilePath + if (-not $downloadResult) { + Write-Host "Failed to download Git. Please try again later or install manually." + } else { + Start-Process -FilePath $gitInstallerFilePath -Args "/VERYSILENT /NORESTART /NOCANCEL /SP-" -Wait + Add-ToEnvPath -NewPath "C:\Program Files\Git\bin" + + Write-Host "Git has been installed." + } +} + +# - 7zip +$7ZipToolName = "7zip" +$7ZipToolDetails = Get-ToolDetails -toolsList $toolsList -toolName $7ZipToolName +Write-Host "$7ZipToolDetails" + +if (Get-Command 7z -ErrorAction SilentlyContinue) { + Write-Host "7-Zip is already installed." +} +else { + Write-Host "Installing 7-Zip..." + + $7ZipInstallerFilePath = "$env:TEMP\7_zip.exe" + Write-Host "$($7ZipToolDetails.mirrors)" + $downloadResult = Invoke-DownloadFileFromAvailableMirrors -mirrorUrls $7ZipToolDetails.mirrors -outfile $7ZipInstallerFilePath + if (-not $downloadResult) { + Write-Host "Failed to download 7-Zip. Please try again later or install manually." + } else { + Start-Process -FilePath $7ZipInstallerFilePath -Args "/S" -Verb RunAs -Wait + Remove-Item $7ZipInstallerFilePath + + # add 7z to PATH + Add-ToEnvPath -NewPath "${env:ProgramFiles}\7-Zip" + } +} + +# - ffpmeg +$ffpmegToolName = "ffmpeg" +$ffpmegToolDetails = Get-ToolDetails -toolsList $toolsList -toolName $ffpmegToolName + +if (Get-Command ffmpeg -ErrorAction SilentlyContinue) { + Write-Host "ffmpeg is already installed." +} else { + Write-Host "ffmpeg is not installed. Installing it." + $ffpmegInstallerFilePath = "C:\ffmpeg.7z" + $downloadResult = Invoke-DownloadFileFromAvailableMirrors -mirrorUrls $ffpmegToolDetails.mirrors -outfile $ffpmegInstallerFilePath + if (-not $downloadResult) { + Write-Host "Failed to download ffmpeg. Please try again later or install manually." + } else { + Write-Host "Extracting $ffpmegInstallerFilePath..." + 7z x -y -o"C:\" "C:\ffmpeg.7z" + + $ffmpegFolder = Get-ChildItem -Path "C:\" -Filter "ffmpeg-*" -Directory + $ffmpegFolder = -join ("C:\", $ffmpegFolder) + #remove ffmpeg folder if exists + if (Test-Path "C:\ffmpeg") { + Remove-Item -Path "C:\ffmpeg" -Recurse -Force + } + Rename-Item -Path "$ffmpegFolder" -NewName "ffmpeg" + + Write-Host "Adding ffmpeg to PATH..." + Add-ToEnvPath -NewPath "C:\ffmpeg\bin" + + Write-Host "ffmpeg is installed" + } +} + +# Disable Edge Auto Updates +Stop-Process -Name "MicrosoftEdgeUpdate" -Force -ErrorAction SilentlyContinue +$edgeUpdatePath = "${env:ProgramFiles(x86)}\Microsoft\EdgeUpdate" +Remove-Item -Path $edgeUpdatePath -Recurse -Force -ErrorAction SilentlyContinue +Write-Host "Edge Update processes terminated and directory removed." + +# - Google Chrome +$chromeToolName = "Google Chrome" +$chromeToolDetails = Get-ToolDetails -toolsList $toolsList -toolName $chromeToolName +$chromeExePath = "C:\Program Files\Google\Chrome\Application\chrome.exe" +$chromeAlias = $chromeToolDetails.alias + +# Check if Google Chrome is already installed by its alias +if (Get-Command $chromeAlias -ErrorAction SilentlyContinue) { + Write-Host "Google Chrome is already installed." +} else { + # Download the installer to the Temp directory + $chromeInstallerFilePath = "$env:TEMP\chrome_installer.exe" + $downloadResult = Invoke-DownloadFileFromAvailableMirrors -mirrorUrls $chromeToolDetails.mirrors -outfile $chromeInstallerFilePath + if (-not $downloadResult) { + Write-Host "Failed to download Google Chrome. Please try again later or install manually." + } else { + # Execute the installer silently with elevated permissions + Start-Process -FilePath $chromeInstallerFilePath -ArgumentList "/silent", "/install" -Verb RunAs -Wait + + # Remove the installer file after installation + Remove-Item -Path $chromeInstallerFilePath + + # Set alias + $setAliasExpression = "Set-Alias -Name $chromeAlias -Value `"$chromeExePath`"" + Add-Content -Path $PROFILE -Value $setAliasExpression + Invoke-Expression $setAliasExpression + + # Add Chrome to the system PATH environment variable + Add-ToEnvPath -NewPath "${env:ProgramFiles}\Google\Chrome\Application" + + # Disable Google Chrome Auto Updates + $chromeRegPath = "HKLM:\SOFTWARE\Policies\Google\Update" + if (-not (Test-Path $chromeRegPath)) { + New-Item -Path $chromeRegPath -Force + } + Set-ItemProperty -Path $chromeRegPath -Name "AutoUpdateCheckPeriodMinutes" -Value 0 + Set-ItemProperty -Path $chromeRegPath -Name "UpdateDefault" -Value 0 + } +} + +# - LibreOffice +$libreOfficeToolName = "LibreOffice" +$libreOfficeToolDetails = Get-ToolDetails -toolsList $toolsList -toolName $libreOfficeToolName + +# Check for LibreOffice installation +$installedVersion = (Get-WmiObject -Query "SELECT * FROM Win32_Product WHERE Name like 'LibreOffice%'").Version +if (-not [string]::IsNullOrWhiteSpace($installedVersion)) { + Write-Host "LibreOffice $version is already installed." +} else { + Write-Host "LibreOffice is not installed. Downloading and installing LibreOffice..." + $libreOfficeInstallerFilePath = "$env:TEMP\libreOffice_installer.exe" + + $downloadResult = Invoke-DownloadFileFromAvailableMirrors -mirrorUrls $libreOfficeToolDetails.mirrors -outfile $libreOfficeInstallerFilePath + if (-not $downloadResult) { + Write-Host "Failed to download LibreOffice. Please try again later or install manually." + } else { + Start-Process "msiexec.exe" -ArgumentList "/i `"$libreOfficeInstallerFilePath`" /quiet" -Wait -NoNewWindow + Write-Host "LibreOffice has been installed." + + # Add LibreOffice to the system PATH environment variable + Add-ToEnvPath -NewPath "C:\Program Files\LibreOffice\program" + } +} + +# - VLC +$vlcToolName = "VLC" +$vlcToolDetails = Get-ToolDetails -toolsList $toolsList -toolName $vlcToolName +$vlcAlias = $vlcToolDetails.alias +$vlcExecutableFilePath = "C:\Program Files\VideoLAN\VLC\vlc.exe" + +# Check if VLC is already installed by checking the VLC command +if (Test-Path $vlcExecutableFilePath) { + Write-Host "VLC is already installed." +} else { + # Download the installer to the Temp directory + $vlcInstallerFilePath = "$env:TEMP\vlc_installer.exe" + $downloadResult = Invoke-DownloadFileFromAvailableMirrors -mirrorUrls $vlcToolDetails.mirrors -outfile $vlcInstallerFilePath + if (-not $downloadResult) { + Write-Host "Failed to download VLC. Please try again later or install manually." + } else { + # Execute the installer silently with elevated permissions + Start-Process -FilePath $vlcInstallerFilePath -ArgumentList "/S" -Verb RunAs -Wait + + # Remove the installer file after installation + Remove-Item -Path $vlcInstallerFilePath + + # Set alias + $setAliasExpression = "Set-Alias -Name $vlcAlias -Value `"$vlcExecutableFilePath`"" + Add-Content -Path $PROFILE -Value $setAliasExpression + Invoke-Expression $setAliasExpression + + # Add VLC to the system PATH environment variable + Add-ToEnvPath -NewPath "C:\Program Files\VideoLAN\VLC" + } +} + +# - GIMP +$gimpToolName = "GIMP" +$gimpToolDetails = Get-ToolDetails -toolsList $toolsList -toolName $gimpToolName +$gimpAlias = $gimpToolDetails.alias +$gimpExecutablePath = "C:\Program Files\GIMP 2\bin\gimp-2.10.exe" + +# Check if GIMP is already installed by checking the GIMP executable path +if (Test-Path $gimpExecutablePath) { + Write-Host "GIMP is already installed." +} else { + # Download the installer to the Temp directory + $gimpInstallerFilePath = "$env:TEMP\gimp_installer.exe" + $downloadResult = Invoke-DownloadFileFromAvailableMirrors -mirrorUrls $gimpToolDetails.mirrors -outfile $gimpInstallerFilePath + if (-not $downloadResult) { + Write-Host "Failed to download GIMP. Please try again later or install manually." + } else { + # Execute the installer silently with elevated permissions + Start-Process -FilePath $gimpInstallerFilePath -ArgumentList "/VERYSILENT /ALLUSERS" -Verb RunAs -Wait + + # Remove the installer file after installation + Remove-Item -Path $gimpInstallerFilePath + + # Set alias + $setAliasExpression = "Set-Alias -Name $gimpAlias -Value `"$gimpExecutablePath`"" + Add-Content -Path $PROFILE -Value $setAliasExpression + Invoke-Expression $setAliasExpression + + # Add GIMP to the system PATH environment variable + Add-ToEnvPath -NewPath "C:\Program Files\GIMP 2\bin" + } +} + +# - VS Code +$vsCodeToolName = "VS Code" +$vsCodeToolDetails = Get-ToolDetails -toolsList $toolsList -toolName $vsCodeToolName +$vsCodeAlias = $gimpToolDetails.alias +$vsCodeExecutablePath = "C:\Users\$env:USERNAME\AppData\Local\Programs\Microsoft VS Code\Code.exe" + +# Check if VS Code is already installed by checking the VS Code executable path +if (Test-Path $vsCodeExecutablePath) { + Write-Host "VS Code is already installed." +} else { + # Download the installer to the Temp directory + $vsCodeInstallerFilePath = "$env:TEMP\VSCodeSetup.exe" + $downloadResult = Invoke-DownloadFileFromAvailableMirrors -mirrorUrls $vsCodeToolDetails.mirrors -outfile $vsCodeInstallerFilePath + if (-not $downloadResult) { + Write-Host "Failed to download VS Code. Please try again later or install manually." + } else { + # Execute the installer silently with elevated permissions + Start-Process -FilePath $vsCodeInstallerFilePath -ArgumentList "/VERYSILENT", "/mergetasks=!runcode" -Verb RunAs -Wait + + # Remove the installer file after installation + Remove-Item -Path $vsCodeInstallerFilePath + + # Set alias + $setAliasExpression = "Set-Alias -Name $vsCodeAlias -Value `"$vsCodeExecutablePath`"" + Add-Content -Path $PROFILE -Value $setAliasExpression + Invoke-Expression $setAliasExpression + + # Add VS Code to the system PATH environment variable + Add-ToEnvPath -NewPath "C:\Users\$env:USERNAME\AppData\Local\Programs\Microsoft VS Code\bin" + + # Disable Visual Studio Code Auto Updates + $vsCodeSettingsPath = "${env:APPDATA}\Code\User\settings.json" + if (-not (Test-Path $vsCodeSettingsPath)) { + # Create the directory if it doesn't exist + $dirPath = Split-Path -Path $vsCodeSettingsPath -Parent + if (-not (Test-Path $dirPath)) { + New-Item -ItemType Directory -Path $dirPath -Force + } + # Initialize an empty hashtable to act as the JSON object + $settingsObj = @{} + $settingsObj["update.mode"] = "none" # Set update mode to none + $settingsObj | ConvertTo-Json | Set-Content $vsCodeSettingsPath + } else { + # If the file exists, modify it + $settingsObj = Get-Content $vsCodeSettingsPath | ConvertFrom-Json + $settingsObj["update.mode"] = "none" + $settingsObj | ConvertTo-Json | Set-Content $vsCodeSettingsPath + } + } +} + +# - Thunderbird +$thunderbirdToolName = "Thunderbird" +$thunderbirdToolDetails = Get-ToolDetails -toolsList $toolsList -toolName $thunderbirdToolName +$thunderbirdAlias = $thunderbirdToolDetails.alias +$thunderbirdExecutablePath = "C:\Program Files\Mozilla Thunderbird\thunderbird.exe" + +# Check if Thunderbird is already installed by checking the Thunderbird executable path +if (Test-Path $thunderbirdExecutablePath) { + Write-Host "Thunderbird is already installed." +} else { + # Download the installer to the Temp directory + $thunderbirdInstallerFilePath = "$env:TEMP\ThunderbirdSetup.exe" + $downloadResult = Invoke-DownloadFileFromAvailableMirrors -mirrorUrls $thunderbirdToolDetails.mirrors -outfile $thunderbirdInstallerFilePath + if (-not $downloadResult) { + Write-Host "Failed to download Thunderbird. Please try again later or install manually." + } else { + # Execute the installer silently with elevated permissions + Start-Process -FilePath $thunderbirdInstallerFilePath -ArgumentList "/S" -Verb RunAs -Wait + + # Remove the installer file after installation + Remove-Item -Path $thunderbirdInstallerFilePath + + # Set alias + $setAliasExpression = "Set-Alias -Name $thunderbirdAlias -Value `"$thunderbirdExecutablePath`"" + Add-Content -Path $PROFILE -Value $setAliasExpression + Invoke-Expression $setAliasExpression + + # Add Thunderbird to the system PATH environment variable + Add-ToEnvPath -NewPath "C:\Program Files\Mozilla Thunderbird" + } +} + +# - Server Setup + +$pythonServerPort = 5000 +$onLogonTaskName = "Server_OnLogon" +$requirementsFile = "$scriptFolder\server\requirements.txt" + +# Ensure pip is updated to the latest version +Install-PythonPackages -Package "pip" -Arguments "--upgrade" + +Install-PythonPackages -Package "wheel" +Install-PythonPackages -Package "pywinauto" + +# Install Python packages from requirements.txt using Python's pip module +if (Test-Path $requirementsFile) { + Write-Host "Installing required Python packages using pip from requirements file..." + Install-PythonPackages -RequirementsPath $requirementsFile +} else { + Write-Error "Requirements file not found: $requirementsFile" + exit +} + +# Add a firewall rule to allow incoming connections on the specified port for the Python executable +$pythonServerRuleName = "PythonHTTPServer-$pythonServerPort" +if (-not (Get-NetFirewallRule -Name $pythonServerRuleName -ErrorAction SilentlyContinue)) { + New-NetFirewallRule -DisplayName $pythonServerRuleName -Direction Inbound -Program $pythonExecutablePath -Protocol TCP -LocalPort $pythonServerPort -Action Allow -Profile Any + Write-Host "Firewall rule added to allow traffic on port $pythonServerPort for Python" +} else { + Write-Host "Firewall rule already exists. $pythonServerRuleName " +} + +$onLogonScriptPath = "$scriptFolder\on-logon.ps1" +# Check if the scheduled task exists before unregistering it +if (Get-ScheduledTask -TaskName $onLogonTaskName -ErrorAction SilentlyContinue) { + Write-Host "Scheduled task $onLogonTaskName already exists." +} else { + Write-Host "Registering new task $onLogonTaskName..." + Register-LogonTask -TaskName $onLogonTaskName -ScriptPath $onLogonScriptPath -LocalUser "Docker" +} + +Start-Sleep -Seconds 10 +Start-ScheduledTask -TaskName $onLogonTaskName \ No newline at end of file diff --git a/computer_use_demo/windowshost/vm/win11setup/setupscripts/tools_config.json b/computer_use_demo/windowshost/vm/win11setup/setupscripts/tools_config.json new file mode 100644 index 0000000..b78db29 --- /dev/null +++ b/computer_use_demo/windowshost/vm/win11setup/setupscripts/tools_config.json @@ -0,0 +1,71 @@ +{ + "Python": { + "mirrors": [ + "https://www.python.org/ftp/python/3.10.0/python-3.10.0-amd64.exe" + ], + "alias": "python" + }, + "git": { + "mirrors": [ + "https://github.com/git-for-windows/git/releases/download/v2.37.1.windows.1/Git-2.37.1-64-bit.exe" + ] + }, + "7zip": { + "mirrors": [ + "https://www.7-zip.org/a/7z2407-x64.exe" + ] + }, + "ffmpeg": { + "mirrors": [ + "https://www.gyan.dev/ffmpeg/builds/ffmpeg-release-essentials.7z" + ] + }, + "Google Chrome": { + "mirrors": [ + "https://dl.google.com/chrome/install/latest/chrome_installer.exe" + ], + "alias": "google-chrome" + }, + "LibreOffice": { + "mirrors": [ + "https://mirror.raiolanetworks.com/tdf/libreoffice/stable/24.8.4/win/x86_64/LibreOffice_24.8.4_Win_x86-64.msi", + "https://mirrors.iu13.net/tdf/libreoffice/stable/24.8.4/win/x86_64/LibreOffice_24.8.4_Win_x86-64.msi", + "https://download.documentfoundation.org/libreoffice/stable/24.8.4/win/x86_64/LibreOffice_24.8.4_Win_x86-64.msi" + ] + }, + "VLC": { + "mirrors": [ + "https://ftp.free.org/mirrors/videolan/vlc/3.0.21/win64/vlc-3.0.21-win64.exe", + "https://mirror.fcix.net/videolan-ftp/vlc/3.0.21/win64/vlc-3.0.21-win64.exe", + "https://mirror.raiolanetworks.com/videolan/vlc/3.0.21/win64/vlc-3.0.21-win64.exe" + ], + "alias": "vlc" + }, + "GIMP": { + "mirrors": [ + "https://www-ftp.lip6.fr/pub/gimp/gimp/v2.10/windows/gimp-2.10.38-setup.exe", + "https://download.gimp.org/gimp/v2.10/windows/gimp-2.10.38-setup.exe", + "https://www-ftp.lip6.fr/pub/gimp/gimp/v2.10/windows/gimp-2.10.0-setup.exe" + ], + "alias": "gimp" + }, + "VS Code": { + "mirrors": [ + "https://update.code.visualstudio.com/latest/win32-x64-user/stable" + ], + "alias": "code" + }, + "Thunderbird": { + "mirrors": [ + "https://download-installer.cdn.mozilla.net/pub/thunderbird/releases/115.12.1/win64/en-US/Thunderbird%20Setup%20115.12.1.exe", + "https://archive.mozilla.org/pub/thunderbird/releases/115.12.1/win64/en-US/Thunderbird%20Setup%20115.12.1.exe" + ], + "alias": "thunderbird" + }, + "Caddy Proxy": { + "mirrors": [ + "https://caddyserver.com/api/download?os=windows&arch=amd64" + ], + "alias": "caddy" + } + } \ No newline at end of file