diff --git a/README.md b/README.md index 512ba28..9902f4b 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,40 @@ +# Using the ansible scripts + Ansible playbooks for deploying classroom computers. Run with something like: ansible-playbook --user -k -K -i inventory.yml setup.py or, for Apple computers: - ansible-playbook --user -k -K -i inventory.yml macsetup.py + +# How classroom computers are prepared at FRI + +The computers are prepared as follows: + - Ask teachers to check their requirements. + - Fix ansible scripts according to the requirements. + - Set up a fresh Windows and Linux install on a VM. + - Use ansible to deploy the software, fixing fresh bugs due to changes in the install processes along the way. + - Fix root filesystem UUIDs to what they were last year so the network-booted menu still works. + - Create partition table image and partition images using [FRI Backup](https://github.com/UL-FRI/ansible_classroom_deploy/tree/main/fri_backup). + - Deploy the images on separate VMs - one for Linux, one for Windows. + - Have teachers check their software in the VMs, report any problems. + - Fix problems on the VMs. + - Create partition images for Windows on one VM, for Linux on the other. + - Deploy the images on a limited number of computers in the classrooms using FRI Backup. + - Test the newly deployed computers, fix identified problems on the VMs. + - Join the Linux VM into AD. One AD account is used for all computers. + - Deploy the partition tables and partitions to the classrooms using [custom ansible scripts](https://github.com/UL-FRI/ansible_classroom_deploy/tree/main/polz_scripts) running FRI Backup and [UDPCast](https://www.udpcast.linux.lu/cmd.html). + - Join Windows into AD. + +# Immutable computers + +Some pieces of modern software assume that a computer is only used by one user who has practically unlimited space in their home folder. Examples of such software are Android Studio, Visual Studio Code, Matlab, Windows Subsystem for Linux (WSUS) and others. Instead of trying to get the software to work, we might just give up and have everyone use the same account on each computer, then wipe all data after they log out / reboot. + +## Windows + +We intend to use [UWF](https://learn.microsoft.com/en-us/windows/configuration/wcd/wcd-unifiedwritefilter). + +## Linux + +We intend to use [snapper](https://wiki.archlinux.org/title/Snapper). + diff --git a/roles/ad_joined/tasks/main_lin.yml b/roles/ad_joined/tasks/main_lin.yml index 2d9b02e..fce6850 100644 --- a/roles/ad_joined/tasks/main_lin.yml +++ b/roles/ad_joined/tasks/main_lin.yml @@ -24,7 +24,7 @@ - name: "Rename" # Racunalnik najprej preimenujmo, da ne bosta v domeni obenem 2 z istim imenom hostname: - name: "{{inventory_hostname}}" + name: "{{inventory_hostname}}-linux" register: hostname_res - name: Check whether we already joined @@ -33,7 +33,7 @@ - name: Join using realmd expect: - command: realm join --user={{ad_join_user}} --computer-ou={{ou_path}} FRI1.UNI-LJ.SI + command: realm join --user={{ad_join_user}} --computer-ou={{ou_path}} {{domain_name}} responses: (?i)Password: "{{ad_join_password}}" ignore_errors: yes diff --git a/roles/ad_joined/tasks/main_win.yml b/roles/ad_joined/tasks/main_win.yml index be252f9..f2de3a9 100644 --- a/roles/ad_joined/tasks/main_win.yml +++ b/roles/ad_joined/tasks/main_win.yml @@ -36,8 +36,8 @@ - name: "Join domain" win_domain_membership: - domain_admin_user: "{{adjoin_user}}" - domain_admin_password: "{{adjoin_password}}" + domain_admin_user: "{{ad_join_user}}" + domain_admin_password: "{{ad_join_password}}" dns_domain_name: "{{domain_name}}" domain_ou_path: "{{ou_path}}" hostname: "{{inventory_hostname}}" diff --git a/roles/eclipse/tasks/main_win.yml b/roles/eclipse/tasks/main_win.yml index 5e39d7c..f26c809 100644 --- a/roles/eclipse/tasks/main_win.yml +++ b/roles/eclipse/tasks/main_win.yml @@ -12,4 +12,4 @@ win_lineinfile: path: "%ProgramFiles%\\Eclipse {{ target_version }}\\eclipse\\eclipse.ini" regexp: "osgi.instance.area.default" - line: "-Dosgi.instance.area.default=H:/Documents/eclipse-workspace" + line: "-Dosgi.instance.area.default=D:/Documents/eclipse-workspace" diff --git a/roles/matlab/tasks/main_win.yml b/roles/matlab/tasks/main_win.yml index e0cddc2..4fd79ff 100644 --- a/roles/matlab/tasks/main_win.yml +++ b/roles/matlab/tasks/main_win.yml @@ -2,7 +2,7 @@ set_fact: target_version: "R2025a_Update_1" license_path: \\ucilnicesmb.fri1.uni-lj.si\ucilnice_d\install\matlab\network.lic - dest_dir: C:\matlab + dest_dir: "{{ large_prog_dir }}\\matlab" - name: Set ISO path set_fact: diff --git a/roles/pycharm/tasks/main_win.yml b/roles/pycharm/tasks/main_win.yml index ae51dbd..2b4516a 100644 --- a/roles/pycharm/tasks/main_win.yml +++ b/roles/pycharm/tasks/main_win.yml @@ -8,4 +8,4 @@ state: upgrade version: "{{ target_version }}" -# TODO set default project directory to H:\something +# TODO set default project directory to {{large_prog_dir}}\something diff --git a/roles/sifive/tasks/main_win.yml b/roles/sifive/tasks/main_win.yml index 1030ce1..e874141 100644 --- a/roles/sifive/tasks/main_win.yml +++ b/roles/sifive/tasks/main_win.yml @@ -5,7 +5,7 @@ - name: Set SiFive Studio install location set_fact: - download_filedest: C:\FreedomStudio-4.18.0.2021-04-1-x86_64-w64-mingw32.zip + download_filedest: "{{ large_prog_dir }}\\FreedomStudio-4.18.0.2021-04-1-x86_64-w64-mingw32.zip" - name: Download SiFive win_command: wget "https://unilj-my.sharepoint.com/:u:/g/personal/bulic_fri1_uni-lj_si/EdiSnJpoClJLtc3AtcAtEQ4BI76_PeDvL-ZmhxG4OEyvig?e=xBRYcb&download=1" -O "{{ download_filedest }}" args: @@ -14,12 +14,12 @@ - name: Unzip SiFive Studio win_unzip: src: "{{ download_filedest }}" - dest: C:\SiFive\ - creates: C:\SiFive\plugins + dest: "{{ large_prog_dir }}\\SiFive\\" + creates: "{{ large_prog_dir }}\\SiFive\\plugins" delete_archive: yes - name: Create desktop shortcut win_shortcut: description: "SiFive Freedom Studio" - src: C:\SiFive\FreedomStudio.exe + src: "{{ large_prog_dir }}\\SiFive\\FreedomStudio.exe" dest: '%public%\Desktop\SiFive.lnk' diff --git a/roles/stm32duino/tasks/main_win.yml b/roles/stm32duino/tasks/main_win.yml index 6779104..cc9fe81 100644 --- a/roles/stm32duino/tasks/main_win.yml +++ b/roles/stm32duino/tasks/main_win.yml @@ -10,13 +10,13 @@ - name: Create STM32Duino directory win_file: - path: "D:\\RAVINOR\\STM32Duino" + path: "{{large_prog_dir}}\\RAVINOR\\STM32Duino" state: directory - name: Install preinstalled STM32Duino & Arduino environment win_get_url: url: "https://unilj-my.sharepoint.com/:u:/g/personal/rozman_fri1_uni-lj_si/EYTFhlsJOFtFk9xsaViP0eYBtILVS24ZF_dlkgodu_L9Nw?e=geavvJ" - dest: "D:\\RAVINOR\\STM32Duino" + dest: "{{large_prog_dir}}\\RAVINOR\\STM32Duino" # TODO Don't know if unzipping is required? diff --git a/roles/xampp/tasks/main_win.yml b/roles/xampp/tasks/main_win.yml index 44bbc71..154de4a 100644 --- a/roles/xampp/tasks/main_win.yml +++ b/roles/xampp/tasks/main_win.yml @@ -1,6 +1,7 @@ - name: Install XAMPP win_chocolatey: name: xampp-81 + install_args: "" - name: Make XAMPP config world writable win_acl: @@ -12,7 +13,7 @@ - name: Add firewall rules for XAMPP win_firewall_rule: name: "Allow inbound traffic for XAMPP: {{ item }}" - program: "%SystemDrive%\\xampp\\{{ item }}" + program: "C:\\xampp\\{{ item }}" action: allow direction: in protocol: tcp diff --git a/setup.yml b/setup.yml index aeaa82b..5596e87 100644 --- a/setup.yml +++ b/setup.yml @@ -13,8 +13,16 @@ debugger: on_failed vars_files: - vars/software_keys.yml + - vars/dirs.yml roles: - - all_classes + # - all_classes + # - fri_base + - android_studio + # - jdk + # - powerdesigner + # - solidworks + # + # - omnetpp # - stm32cube # - processing # - powerdesigner diff --git a/unattended_install/win/autounattend.xml b/unattended_install/win/autounattend.xml index bfb33fe..6ee0db5 100644 --- a/unattended_install/win/autounattend.xml +++ b/unattended_install/win/autounattend.xml @@ -1,13 +1,13 @@ - + en-US - 0409:00000424 + 0409:00000424;0409:00000409 en-US en-US en-US @@ -45,7 +45,6 @@ ucilnica - Central Europe Standard Time @@ -76,7 +75,7 @@ - 0409:00000424 + 0409:00000424;0409:00000409 en-US en-US en-US @@ -145,6 +144,183 @@ foreach( $file in $Document.unattend.Extensions.File ) { [System.IO.File]::WriteAllBytes( $path, $bytes ); } + +$selectors = @( + 'Microsoft.Microsoft3DViewer'; + 'Microsoft.BingSearch'; + 'Microsoft.WindowsCalculator'; + 'Microsoft.WindowsCamera'; + 'Microsoft.WindowsAlarms'; + 'Microsoft.549981C3F5F10'; + 'Microsoft.Windows.DevHome'; + 'MicrosoftCorporationII.MicrosoftFamily'; + 'Microsoft.WindowsFeedbackHub'; + 'Microsoft.Edge.GameAssist'; + 'Microsoft.Getstarted'; + 'Microsoft.WindowsMaps'; + 'Microsoft.MixedReality.Portal'; + 'Microsoft.BingNews'; + 'Microsoft.MicrosoftOfficeHub'; + 'Microsoft.Paint'; + 'Microsoft.Windows.Photos'; + 'Microsoft.PowerAutomateDesktop'; + 'MicrosoftCorporationII.QuickAssist'; + 'Microsoft.SkypeApp'; + 'Microsoft.MicrosoftSolitaireCollection'; + 'Microsoft.MicrosoftStickyNotes'; + 'Microsoft.Todos'; + 'Microsoft.Wallet'; + 'Microsoft.YourPhone'; +); +$getCommand = { + Get-AppxProvisionedPackage -Online; +}; +$filterCommand = { + $_.DisplayName -eq $selector; +}; +$removeCommand = { + [CmdletBinding()] + param( + [Parameter( Mandatory, ValueFromPipeline )] + $InputObject + ); + process { + $InputObject | Remove-AppxProvisionedPackage -AllUsers -Online -ErrorAction 'Continue'; + } +}; +$type = 'Package'; +$logfile = 'C:\Windows\Setup\Scripts\RemovePackages.log'; +& { + $installed = & $getCommand; + foreach( $selector in $selectors ) { + $result = [ordered] @{ + Selector = $selector; + }; + $found = $installed | Where-Object -FilterScript $filterCommand; + if( $found ) { + $result.Output = $found | & $removeCommand; + if( $? ) { + $result.Message = "$type removed."; + } else { + $result.Message = "$type not removed."; + $result.Error = $Error[0]; + } + } else { + $result.Message = "$type not installed."; + } + $result | ConvertTo-Json -Depth 3 -Compress; + } +} *>&1 >> $logfile; + + +$selectors = @( + 'Print.Fax.Scan'; + 'Language.Handwriting'; + 'Browser.InternetExplorer'; + 'MathRecognizer'; + 'OneCoreUAP.OneSync'; + 'Microsoft.Windows.MSPaint'; + 'Microsoft.Windows.PowerShell.ISE'; + 'App.Support.QuickAssist'; + 'Language.Speech'; + 'Language.TextToSpeech'; + 'App.StepsRecorder'; + 'Hello.Face.18967'; + 'Hello.Face.Migration.18967'; + 'Hello.Face.20134'; + 'Microsoft.Windows.WordPad'; +); +$getCommand = { + Get-WindowsCapability -Online | Where-Object -Property 'State' -NotIn -Value @( + 'NotPresent'; + 'Removed'; + ); +}; +$filterCommand = { + ($_.Name -split '~')[0] -eq $selector; +}; +$removeCommand = { + [CmdletBinding()] + param( + [Parameter( Mandatory, ValueFromPipeline )] + $InputObject + ); + process { + $InputObject | Remove-WindowsCapability -Online -ErrorAction 'Continue'; + } +}; +$type = 'Capability'; +$logfile = 'C:\Windows\Setup\Scripts\RemoveCapabilities.log'; +& { + $installed = & $getCommand; + foreach( $selector in $selectors ) { + $result = [ordered] @{ + Selector = $selector; + }; + $found = $installed | Where-Object -FilterScript $filterCommand; + if( $found ) { + $result.Output = $found | & $removeCommand; + if( $? ) { + $result.Message = "$type removed."; + } else { + $result.Message = "$type not removed."; + $result.Error = $Error[0]; + } + } else { + $result.Message = "$type not installed."; + } + $result | ConvertTo-Json -Depth 3 -Compress; + } +} *>&1 >> $logfile; + + +$selectors = @( + 'Microsoft-RemoteDesktopConnection'; + 'Recall'; +); +$getCommand = { + Get-WindowsOptionalFeature -Online | Where-Object -Property 'State' -NotIn -Value @( + 'Disabled'; + 'DisabledWithPayloadRemoved'; + ); +}; +$filterCommand = { + $_.FeatureName -eq $selector; +}; +$removeCommand = { + [CmdletBinding()] + param( + [Parameter( Mandatory, ValueFromPipeline )] + $InputObject + ); + process { + $InputObject | Disable-WindowsOptionalFeature -Online -Remove -NoRestart -ErrorAction 'Continue'; + } +}; +$type = 'Feature'; +$logfile = 'C:\Windows\Setup\Scripts\RemoveFeatures.log'; +& { + $installed = & $getCommand; + foreach( $selector in $selectors ) { + $result = [ordered] @{ + Selector = $selector; + }; + $found = $installed | Where-Object -FilterScript $filterCommand; + if( $found ) { + $result.Output = $found | & $removeCommand; + if( $? ) { + $result.Message = "$type removed."; + } else { + $result.Message = "$type not removed."; + $result.Error = $Error[0]; + } + } else { + $result.Message = "$type not installed."; + } + $result | ConvertTo-Json -Depth 3 -Compress; + } +} *>&1 >> $logfile; + & { foreach( $letter in 'DEFGHIJKLMNOPQRSTUVWXYZ'.ToCharArray() ) { @@ -192,20 +368,16 @@ $shellParams = @{ } New-ItemProperty @shellParams -# Set time to UTC +# Set real-time clock to UTC $shellParams = @{ - Path = 'HKLM:\SYSTEM\CurrentControlSet\Control\TimeZoneInformation' - Name = 'RealTimeIsUniversal' - Value = '00000001' - PropertyType = 'dword' - Force = $true + Path = 'HKLM:\SYSTEM\CurrentControlSet\Control\TimeZoneInformation' + Name = 'RealTimeIsUniversal' + Value = '1' + PropertyType = 'DWord' } New-ItemProperty @shellParams -Set-Service -Name sshd -StartupType Automatic -Status Running - - Set-Service -Name sshd -StartupType Automatic -Status Running @@ -213,6 +385,18 @@ $scripts = @( { reg.exe add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\OOBE" /v BypassNRO /t REG_DWORD /d 1 /f; }; + { + Remove-Item -LiteralPath 'Registry::HKLM\Software\Microsoft\WindowsUpdate\Orchestrator\UScheduler_Oobe\DevHomeUpdate' -Force -ErrorAction 'SilentlyContinue'; + }; + { + Get-Content -LiteralPath 'C:\Windows\Setup\Scripts\RemovePackages.ps1' -Raw | Invoke-Expression; + }; + { + Get-Content -LiteralPath 'C:\Windows\Setup\Scripts\RemoveCapabilities.ps1' -Raw | Invoke-Expression; + }; + { + Get-Content -LiteralPath 'C:\Windows\Setup\Scripts\RemoveFeatures.ps1' -Raw | Invoke-Expression; + }; { net.exe accounts /maxpwage:UNLIMITED; }; @@ -220,9 +404,15 @@ $scripts = @( netsh.exe advfirewall firewall set rule group="@FirewallAPI.dll,-28752" new enable=Yes; reg.exe add "HKLM\SYSTEM\CurrentControlSet\Control\Terminal Server" /v fDenyTSConnections /t REG_DWORD /d 0 /f; }; + { + icacls.exe C:\ /remove:g "*S-1-5-11" + }; { reg.exe add "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Power" /v HiberbootEnabled /t REG_DWORD /d 0 /f; }; + { + reg.exe add "HKLM\SOFTWARE\Policies\Microsoft\Dsh" /v AllowNewsAndInterests /t REG_DWORD /d 0 /f; + }; { reg.exe add "HKLM\SYSTEM\CurrentControlSet\Control\BitLocker" /v "PreventDeviceEncryption" /t REG_DWORD /d 1 /f; }; @@ -258,6 +448,9 @@ $scripts = @( { Set-WinHomeLocation -GeoId 212; }; + { + Get-AppxPackage -Name 'Microsoft.Windows.Ai.Copilot.Provider' | Remove-AppxPackage; + }; { Set-ItemProperty -LiteralPath 'Registry::HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced' -Name 'LaunchTo' -Type 'DWord' -Value 1; }; @@ -287,6 +480,9 @@ $scripts = @( $scripts = @( + { + reg.exe add "HKU\DefaultUser\Software\Policies\Microsoft\Windows\WindowsCopilot" /v TurnOffWindowsCopilot /t REG_DWORD /d 1 /f; + }; { reg.exe add "HKU\DefaultUser\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced" /v "HideFileExt" /t REG_DWORD /d 0 /f; }; @@ -325,9 +521,6 @@ $scripts = @( { Get-Content -LiteralPath 'C:\Windows\Setup\Scripts\VirtIoGuestTools.ps1' -Raw | Invoke-Expression; }; - { - Get-Content -LiteralPath 'C:\Windows\Setup\Scripts\unattend-02.ps1' -Raw | Invoke-Expression; - }; ); & {