diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index caf3325..0000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "version": "0.2.0", - "configurations": [ - { - "type": "PowerShell", - "request": "launch", - "name": "PowerShell Launch Current File", - "script": "${file}", - "args": [], - "cwd": "${file}" - }, - { - "type": "PowerShell", - "request": "launch", - "name": "PowerShell Launch Current File w/Args Prompt", - "script": "${file}", - "args": [ - "${command:SpecifyScriptArgs}" - ], - "cwd": "${file}" - }, - { - "type": "PowerShell", - "request": "attach", - "name": "PowerShell Attach to Host Process", - "processId": "${command:PickPSHostProcess}", - "runspaceId": 1 - }, - { - "type": "PowerShell", - "request": "launch", - "name": "PowerShell Interactive Session", - "cwd": "${workspaceRoot}" - } - ] -} \ No newline at end of file diff --git a/Installer/Install-PSD.ps1 b/Installer/Install-PSD.ps1 new file mode 100644 index 0000000..96cd24d --- /dev/null +++ b/Installer/Install-PSD.ps1 @@ -0,0 +1,183 @@ +# +# Install.ps1 +# version 1.0 + +#Requires -RunAsAdministrator + +Param( + $psDeploymentFolder = "NA", + $psDeploymentShare = "NA", + [Switch]$Upgrade +) + +if($psDeploymentFolder -eq 'NA'){ + Write-Error "You have not specified the -psDeploymentfolder" + Write-Error "Run the installer script again and specify -psDeploymentFolder and -psDeploymentShare" + Break +} + +if($psDeploymentShare -eq 'NA'){ + Write-Error "You have not specified the -psDeploymentfolder" + Write-Error "Run the installer script again and specify -psDeploymentFolder and -psDeploymentShare" + Break +} + +# Set vars +$install = Split-Path -Path "$PSScriptRoot" +$verbosePreference = "Continue" + +if($Upgrade) +{ + Write-Verbose "Installer running in upgrade mode" +} + + +# Utility function to copy folders (using XCOPY) +function Copy-PSDFolder +{ + param ( + [Parameter(Mandatory=$True,Position=1)] + [string] $source, + [Parameter(Mandatory=$True,Position=2)] + [string] $destination + ) + + $s = $source.TrimEnd("\") + $d = $destination.TrimEnd("\") + Write-Verbose "Copying folder $source to $destination using XCopy" + & xcopy $s $d /s /e /v /d /y /i | Out-Null +} + +#TODO - Check for ADK installed and version +$mdtADK = (Get-ItemProperty HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\* | Where-Object {$_.DisplayName -like "Windows Assessment and Deployment Kit*"}).DisplayVersion +Write-Verbose "ADK installed version== $mdtADK" + +$mdtADKPE = (Get-ItemProperty HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\* | Where-Object {$_.DisplayName -like "Windows Assessment and Deployment Kit Windows*"}).DisplayVersion +Write-Verbose "WinPE Addon for ADK(only for ADK1809 or above)== $mdtADKPE" + +#TODO - Check for MDT installed and version +$mdtVer = ((Get-ItemProperty HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\* | Where-Object {$_.DisplayName -like "Microsoft Deployment Toolkit*"}).Displayname) +Write-Verbose "MDT installed version== $mdtVer" + +# Create the folder and share +if (Test-Path -path $psDeploymentFolder) +{ + if(!($Upgrade)) + { + Write-Warning "PSD Folder already exist, will break" + BREAK + } +} +else +{ + New-Item -Path $psDeploymentFolder -ItemType Directory + New-SmbShare -Name $psDeploymentShare -Path $psDeploymentFolder -FullAccess Administrators +} + + +# Load the MDT PowerShell provider +$mdtDir = $(Get-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Deployment 4' -Name Install_Dir).Install_Dir +Write-Verbose "MDT installation directory: $mdtDir" +Import-Module "$($mdtDir)Bin\MicrosoftDeploymentToolkit.psd1" + +# Create the deployment share at the specified path +#TODO - check for existig PSDdrive if a re-run +if(!($Upgrade)) +{ + New-PSDrive -Name "PSD" -PSProvider "MDTProvider" -Root $psDeploymentFolder -Description "PSD Deployment Share" -NetworkPath "\\$env:COMPUTERNAME\$psDeploymentShare" | add-MDTPersistentDrive +} + +# Copy the scripts +Copy-PSDFolder "$install\Scripts\*.ps1" "$psDeploymentFolder\Scripts" +Dir "$psDeploymentFolder\Scripts\*.ps1" | Unblock-File +Copy-PSDFolder "$install\Scripts\*.xaml" "$psDeploymentFolder\Scripts" +Dir "$psDeploymentFolder\Scripts\*.xaml" | Unblock-File + +# Copy the templates +Copy-PSDFolder "$install\Templates" "$psDeploymentFolder\Templates" +Dir "$psDeploymentFolder\Templates\*" | Unblock-File + +# Copy the script modules to the right places +write-verbose "Copying PSD Modules to $psdeploymentfolder......." +"PSDGather", "PSDDeploymentShare", "PSDUtility", "PSDWizard" | % { + if ((Test-Path "$psDeploymentFolder\Tools\Modules\$_") -eq $false) + { + $null = New-Item "$psDeploymentFolder\Tools\Modules\$_" -ItemType directory + } + Write-Verbose "Copying module $_ to $psDeploymentFolder\Tools\Modules" + Copy-Item "$install\Scripts\$_.psm1" "$psDeploymentFolder\Tools\Modules\$_" + Dir "$psDeploymentFolder\Tools\Modules\$_\*" | Unblock-File +} + +# Copy the provider module files +Write-Verbose "Copying MDT provider files to $psDeploymentFolder\Tools\Modules" +if ((Test-Path "$psDeploymentFolder\Tools\Modules\Microsoft.BDD.PSSnapIn") -eq $false) +{ + $null = New-Item "$psDeploymentFolder\Tools\Modules\Microsoft.BDD.PSSnapIn" -ItemType directory +} +Copy-Item "$($mdtDir)Bin\Microsoft.BDD.PSSnapIn.dll" "$psDeploymentFolder\Tools\Modules\Microsoft.BDD.PSSnapIn" +Copy-Item "$($mdtDir)Bin\Microsoft.BDD.PSSnapIn.dll.config" "$psDeploymentFolder\Tools\Modules\Microsoft.BDD.PSSnapIn" +Copy-Item "$($mdtDir)Bin\Microsoft.BDD.PSSnapIn.dll-help.xml" "$psDeploymentFolder\Tools\Modules\Microsoft.BDD.PSSnapIn" +Copy-Item "$($mdtDir)Bin\Microsoft.BDD.PSSnapIn.Format.ps1xml" "$psDeploymentFolder\Tools\Modules\Microsoft.BDD.PSSnapIn" +Copy-Item "$($mdtDir)Bin\Microsoft.BDD.PSSnapIn.Types.ps1xml" "$psDeploymentFolder\Tools\Modules\Microsoft.BDD.PSSnapIn" +Copy-Item "$($mdtDir)Bin\Microsoft.BDD.Core.dll" "$psDeploymentFolder\Tools\Modules\Microsoft.BDD.PSSnapIn" +Copy-Item "$($mdtDir)Bin\Microsoft.BDD.Core.dll.config" "$psDeploymentFolder\Tools\Modules\Microsoft.BDD.PSSnapIn" +Copy-Item "$($mdtDir)Bin\Microsoft.BDD.ConfigManager.dll" "$psDeploymentFolder\Tools\Modules\Microsoft.BDD.PSSnapIn" + +# Copy the provider template files +Write-Verbose "Copying PSD templates to $psDeploymentFolder\Templates" +if ((Test-Path "$psDeploymentFolder\Templates") -eq $false) +{ + $null = New-Item "$psDeploymentFolder\Templates" +} +Copy-Item "$($mdtDir)Templates\Groups.xsd" "$psDeploymentFolder\Templates" +Copy-Item "$($mdtDir)Templates\Medias.xsd" "$psDeploymentFolder\Templates" +Copy-Item "$($mdtDir)Templates\OperatingSystems.xsd" "$psDeploymentFolder\Templates" +Copy-Item "$($mdtDir)Templates\Packages.xsd" "$psDeploymentFolder\Templates" +Copy-Item "$($mdtDir)Templates\SelectionProfiles.xsd" "$psDeploymentFolder\Templates" +Copy-Item "$($mdtDir)Templates\TaskSequences.xsd" "$psDeploymentFolder\Templates" +Copy-Item "$($mdtDir)Templates\Applications.xsd" "$psDeploymentFolder\Templates" +Copy-Item "$($mdtDir)Templates\Drivers.xsd" "$psDeploymentFolder\Templates" +Copy-Item "$($mdtDir)Templates\Groups.xsd" "$psDeploymentFolder\Templates" +Copy-Item "$($mdtDir)Templates\LinkedDeploymentShares.xsd" "$psDeploymentFolder\Templates" + +# Update the ISO properties +if(!($Upgrade)) +{ + Write-Verbose "Updating PSD ISO properties" + Set-ItemProperty PSD: -Name "Boot.x86.LiteTouchISOName" -Value "PSDLiteTouch_x86.iso" + Set-ItemProperty PSD: -Name "Boot.x86.LiteTouchWIMDescription" -Value "PowerShell Deployment Boot Image (x86)" + Set-ItemProperty PSD: -Name "Boot.x64.LiteTouchISOName" -Value "PSDLiteTouch_x64.iso" + Set-ItemProperty PSD: -Name "Boot.x64.LiteTouchWIMDescription" -Value "PowerShell Deployment Boot Image (x64)" +} + +#Add ZTIGather.XML to correct folder (The file is missing after install) (added by admminy) +Write-verbose "Adding ZTIGather.XML to correct folder" +Copy-Item "$($mdtDir)Templates\Distribution\Scripts\ZTIGather.xml" "$psDeploymentFolder\Tools\Modules\PSDGather" + +#Add UNC Path to DeploymentShare (TBA) + +#Create folders +Write-verbose "Creating Logs folder in $psdeploymentshare" +$null = New-Item -ItemType directory -Path $psDeploymentFolder\Logs -Force + +Write-verbose "Creating Dynamics Logs sub-folder in $psdeploymentshare" +$null = New-Item -ItemType directory -Path $psDeploymentFolder\Logs\Dyn -Force + +Write-verbose "Creating DriverSources folder in $psdeploymentshare" +$null = New-Item -ItemType directory -Path $psDeploymentFolder\DriverSources -Force + +Write-verbose "Creating DriverPackages folder in $psdeploymentshare" +$null = New-Item -ItemType directory -Path $psDeploymentFolder\DriverPackages -Force + +#Relax Permissions on DeploymentShare (added admminy) +if(!($Upgrade)) +{ + Write-verbose "Relaxing permissons on $psDeploymentShare" + $null = icacls $psDeploymentFolder /grant '"Users":(OI)(CI)(RX)' + $null = icacls $psDeploymentFolder /grant '"Administrators":(OI)(CI)(F)' + $null = icacls $psDeploymentFolder /grant '"SYSTEM":(OI)(CI)(F)' + Grant-SmbShareAccess -Name $psDeploymentShare -AccountName "EVERYONE" -AccessRight Change -Force + Revoke-SmbShareAccess -Name $psDeploymentShare -AccountName "CREATOR OWNER" -Force +} + diff --git a/Installer/Install.ps1 b/Installer/Install.ps1 deleted file mode 100644 index f5b3197..0000000 --- a/Installer/Install.ps1 +++ /dev/null @@ -1,99 +0,0 @@ -# -# Install.ps1 -# - -$verbosePreference = "Continue" - -# Utility function to copy folders (using XCOPY) -function Copy-PSDFolder -{ - param ( - [Parameter(Mandatory=$True,Position=1)] - [string] $source, - [Parameter(Mandatory=$True,Position=2)] - [string] $destination - ) - - $s = $source.TrimEnd("\") - $d = $destination.TrimEnd("\") - Write-Verbose "Copying folder $source to $destination using XCopy" - & xcopy $s $d /s /e /v /d /y /i | Out-Null -} - -# Location and share name for the new deployment share -$psDeploymentFolder = "C:\PSDeploymentShare" -$psDeploymentShare = "PSDeploymentShare$" - -# Create the folder and share -New-Item -Path $psDeploymentFolder -ItemType directory -New-SmbShare -Name $psDeploymentShare -Path $psDeploymentFolder -FullAccess Administrators - -# Find the folder this script is in -$install = Split-Path -Path "$PSScriptRoot" - -# Load the MDT PowerShell provider -$mdtDir = $(Get-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Deployment 4' -Name Install_Dir).Install_Dir -Write-Verbose "MDT installation directory: $mdtDir" -Import-Module "$($mdtDir)Bin\MicrosoftDeploymentToolkit.psd1" - -# Create the deployment share at the specified path -$null = New-PSDrive -Name PSD -PSProvider MDTProvider -Root $psDeploymentFolder - -# Copy the scripts -Copy-PSDFolder "$install\Scripts\*.ps1" "$psDeploymentFolder\Scripts" -Dir "$psDeploymentFolder\Scripts\*.ps1" | Unblock-File -Copy-PSDFolder "$install\Scripts\*.xaml" "$psDeploymentFolder\Scripts" -Dir "$psDeploymentFolder\Scripts\*.xaml" | Unblock-File - -# Copy the templates -Copy-PSDFolder "$install\Templates" "$psDeploymentFolder\Templates" -Dir "$psDeploymentFolder\Templates\*" | Unblock-File - -# Copy the script modules to the right places -"PSDUtility", "PSDDeploymentShare", "PSDGather", "PSDWizard" | % { - if ((Test-Path "$psDeploymentFolder\Tools\Modules\$_") -eq $false) - { - $null = New-Item "$psDeploymentFolder\Tools\Modules\$_" -ItemType directory - } - Write-Verbose "Copying module $_ to $psDeploymentFolder\Tools\Modules" - Copy-Item "$install\Scripts\$_.psm1" "$psDeploymentFolder\Tools\Modules\$_" - Dir "$psDeploymentFolder\Tools\Modules\$_\*" | Unblock-File -} - -# Copy the provider module files -Write-Verbose "Copying MDT provider files to $psDeploymentFolder\Tools\Modules" -if ((Test-Path "$psDeploymentFolder\Tools\Modules\Microsoft.BDD.PSSnapIn") -eq $false) -{ - $null = New-Item "$psDeploymentFolder\Tools\Modules\Microsoft.BDD.PSSnapIn" -ItemType directory -} -Copy-Item "$($mdtDir)Bin\Microsoft.BDD.PSSnapIn.dll" "$psDeploymentFolder\Tools\Modules\Microsoft.BDD.PSSnapIn" -Copy-Item "$($mdtDir)Bin\Microsoft.BDD.PSSnapIn.dll.config" "$psDeploymentFolder\Tools\Modules\Microsoft.BDD.PSSnapIn" -Copy-Item "$($mdtDir)Bin\Microsoft.BDD.PSSnapIn.dll-help.xml" "$psDeploymentFolder\Tools\Modules\Microsoft.BDD.PSSnapIn" -Copy-Item "$($mdtDir)Bin\Microsoft.BDD.PSSnapIn.Format.ps1xml" "$psDeploymentFolder\Tools\Modules\Microsoft.BDD.PSSnapIn" -Copy-Item "$($mdtDir)Bin\Microsoft.BDD.PSSnapIn.Types.ps1xml" "$psDeploymentFolder\Tools\Modules\Microsoft.BDD.PSSnapIn" -Copy-Item "$($mdtDir)Bin\Microsoft.BDD.Core.dll" "$psDeploymentFolder\Tools\Modules\Microsoft.BDD.PSSnapIn" -Copy-Item "$($mdtDir)Bin\Microsoft.BDD.Core.dll.config" "$psDeploymentFolder\Tools\Modules\Microsoft.BDD.PSSnapIn" -Copy-Item "$($mdtDir)Bin\Microsoft.BDD.ConfigManager.dll" "$psDeploymentFolder\Tools\Modules\Microsoft.BDD.PSSnapIn" - -# Copy the provider template files -Write-Verbose "Copying templates to $psDeploymentFolder\Templates" -if ((Test-Path "$psDeploymentFolder\Templates") -eq $false) -{ - $null = New-Item "$psDeploymentFolder\Templates" -} -Copy-Item "$($mdtDir)Templates\Groups.xsd" "$psDeploymentFolder\Templates" -Copy-Item "$($mdtDir)Templates\Medias.xsd" "$psDeploymentFolder\Templates" -Copy-Item "$($mdtDir)Templates\OperatingSystems.xsd" "$psDeploymentFolder\Templates" -Copy-Item "$($mdtDir)Templates\Packages.xsd" "$psDeploymentFolder\Templates" -Copy-Item "$($mdtDir)Templates\SelectionProfiles.xsd" "$psDeploymentFolder\Templates" -Copy-Item "$($mdtDir)Templates\TaskSequences.xsd" "$psDeploymentFolder\Templates" -Copy-Item "$($mdtDir)Templates\Applications.xsd" "$psDeploymentFolder\Templates" -Copy-Item "$($mdtDir)Templates\Drivers.xsd" "$psDeploymentFolder\Templates" -Copy-Item "$($mdtDir)Templates\Groups.xsd" "$psDeploymentFolder\Templates" -Copy-Item "$($mdtDir)Templates\LinkedDeploymentShares.xsd" "$psDeploymentFolder\Templates" - -# Update the ISO properties -Set-ItemProperty PSD: -Name "Boot.x86.LiteTouchISOName" -Value "PSDLiteTouch_x86.iso" -Set-ItemProperty PSD: -Name "Boot.x86.LiteTouchWIMDescription" -Value "PowerShell Deployment Boot Image (x86)" -Set-ItemProperty PSD: -Name "Boot.x64.LiteTouchISOName" -Value "PSDLiteTouch_x64.iso" -Set-ItemProperty PSD: -Name "Boot.x64.LiteTouchWIMDescription" -Value "PowerShell Deployment Boot Image (x64)" diff --git a/Installer/New-PSDWebInstance.ps1 b/Installer/New-PSDWebInstance.ps1 new file mode 100644 index 0000000..bbfd111 --- /dev/null +++ b/Installer/New-PSDWebInstance.ps1 @@ -0,0 +1,715 @@ +<# +.Synopsis + This script is designed to work with the PSD Toolkit to install and configure IIS as needed for the solution to deploy Windows over the internet. + This script will install the IIS feature set and the components required for WEBDav + +.Description + This script was written by Jordan Benzing @JordanTheITGuy in partnership with TrueSec and 2Pint. This script is for the friends of MDT deployment tools + and is responsible for making the required IIS components work for PSD + + +.LINK + https://github.com/FriendsOfMDT/PSD + +.NOTES + FileName: New-PSDIISInstance.PS1 + Author: Jordan Benzing + Contact: @JordanTheITGuy + Created: 2019-04-15 + Updated: 2019-04-18 + + Version 0.0.0 (2019-04-15) Wrote out framework for code base imported standard helper functions + Version 0.1.0 (2019-04-15)- Created a functional version of the script installs IIS, installs WebDav - reboots server to complete setup and then starts WEbDave Services + Version 0.1.1 (2019-04-17) - Created switches for "install" v.s. Configure + - Created New function that does the configuration so that we can add all configuration changes in one place + - Created a switch for reboot + - Blocked the behaviour so that someone cannot call Install AND configure - without a reboot + Version 0.2.0 (2019-04-18) - Updated the Logic for the configure step so it can be modified seperately from the installation step + - Implemented a check that prevents you from running the script if the script is targeting the local server (Can't handle reboot) + Version 0.3.1 (2019-04-18) - Use a variable LIST of all features that need to be installed now for IIS + - Wrote out a function for confiugre + - crossed off numerous TODO functioanlity checks + - Some Code optimization from repeat sections + Version 0.3.2 (2019-04-19) - Increased the functionality of the reboot handler + - Now ALLOW you to reboot the local computer but only if Configure wasn't requested + - Now prompts to confirm the reboot + Version 0.3.3 (2019-04-19) - Now retrieves network information about the client starting the install and the target machine and logs it. + - May include a "debug" option in the WRite-PSD log function in the future.. + Version 0.3.4 (2019-04-19) - Updated from PSD to PSD - on all the function + - Moved the start-log function back to the begin block to make it work properly. + Version 0.3.5 (2019-04-22) - Corrected the process currently for the installtion/configuration steps. Validation of final testing steps Now needs to occur. + Version 0.4.0 (2019-04-23) - Removed the TODO: Block from the script header. All to do's will now be finalized trough GITHUB project workflow. + - Re-organized the code layout/structure for readabiltiy with regions in process block + - Refactored the install code to use a list install instead of feature by feature + - NOTE - commented out the old code did not remove - want to save as an "Advanced" installer methd for troubleshooting which feature fails in the future + - Introduced time tracking on the script to log the duration of the install or configuration. + Version 0.4.1 (2019-04-23) - Confirmed Install Command works - Still no complete resolution on Logging function not erroring out while the script is running. + - Confirmed Configure Works - Now need to enhance functionality + - Resolved Issue Tracked on Github #1 + Version 0.4.2 (2019-04-24) - Resolved The following Github Issues + - #3 Install Script Doesn't Check if Already Run + - #10 New-PSDWebinstance.ps1 cannot run -configure multiple times + - #9 Variable names and check for folder + - #8 Uppercase N in New + - #7 MIME type being set a second time causes terminating error -> Interealated to Numbers #3 and #10 + Version 0.4.3 (2019-04-24) - Updated the psDeploymentShare parameter to meet the criteria of being called psDeploymentFolder per Bug #9 + Version 0.4.4 (2019-04-24) - Updated the {} structure to follow the styling that Niehaus is using. Per Bug #6 + - Validated Code works in Version - Dev Test SignOff + - Added parameter information to the powerShell Get-Help block. + - Added functional examples to the PowerShell Get-Help Block + Version 0.4.5 (2019-04-25) - Discovered a Microsoft Bug with IIS PowerShell Cmdlets developed a way around it by implementing an older command style to first populate the needed + - Attributes and then move on. This related to Bug #12 + Version 0.4.6 (2019-05-01) - Configured same settings on the Default Web Site related to WebDav + - Logging an issue with not being able to hide the AppCMD output will update an issue tracking number tomorrow + + +.PARAMETER Install + This parameter will run the installation for all of the features that are requried for the PowerShell install. Currently this parameter will NOT run if IIS is already installed. + Additional development time has been requested to allow it to add the features that are MISSING if IIS is already installed on the server. + +.PARAMETER Configure + This parameter will run the configuration steps for the virtual directories. This parameter can be used multiple times to configure multiple directories for PSD. + +.PARAMETER psDeploymentFolder + This parameter is mandatory if the configure parameter has been specified. This parameter is the path to the directory that is used by the virtual directory. Currently this has only been tested locally and not + with a UNC Path. + +.PARAMETER psVirtualDirectory + This parameter is mandatory if the configure parameter has been specified. This parameter is the virtual directories name that will be listed under "Default Web Site" Currently we only support installing a + virtual directory under the default website. No plans have been made to support anything else. + +.PARAMETER ComputerName + This parameter specifies the target server the above options should run against. Currently ComputerName is only supported for the INSTALL command and not the configure command. This is planned to be added in + Revision 2.0. + +.EXAMPLE + .\New-PSDWebInstance.ps1 -install + This will install the IIS and WebDAV features on the local server + +.EXAMPLE + .\New-PSDWebInstance.Ps1 -Configure -PSvirtualDirectory "PSDExample01" -psDeploymentFolder "C:\MDT\MyDeploymentShare" + This will configure the needed webDAV components for the local server. + +#> + +[CmdletBinding(DefaultParameterSetName='None')] +param( + [Parameter(HelpMessage="Please enter the server name you would like to install the IIS/WEBDAV Role on if no option is selected it will assume a local install")] + [string]$ComputerName = $ENV:COMPUTERNAME, + [Parameter(HelpMessage = "To complete the installation of WebDav a reboot is required. If you woud like the script to perform the reboot then make this true.")] + [switch]$AllowReboot, + [Parameter(HelpMessage = "Use this flag to run the installer")] + [switch]$Install, + [Parameter(HelpMessage = "Use this flag to run the post install configuration. This flag should only be run if the install has already been run for IIS/WEBDAV",ParameterSetName ='Config',Mandatory=$false)] + [switch]$Configure, + [Parameter(HelpMessage = "Use this flag to specify the MDT Share Path - Note if you do not provide one the script WILL Error OUT.",ParameterSetName ='Config',Mandatory=$True)] + [string]$psDeploymentFolder, + [parameter(HelpMessage = "Use this flag to specifiy the NAME of the PSD - NOTE - if you do not provide one the defualt value will be used.",ParameterSetName ='Config',Mandatory=$True)] + [string]$psVirtualDirectory +) +begin +{ + +############################################ +#region RequirementCheck +$currentPrincipal = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent()) +if(!($currentPrincipal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator))) +{ + Write-Error -Message "You must run this script as an administrator with admin permissions" +} +#endregion RequirementCheck +############################################ + +############################################ +#region HelperFunctions +function Test-PSDConnectivity +#Test Connection function. All network tests should be added to this for a full connection test. Returns true or false. +{ + [CmdletBinding()] + param + ( + [parameter(Mandatory = $true)] + [string]$ComputerName + ) + Try + #Try each connection test. If there is a connection test that you do not want to use remove it by commenting out the line. + { + Test-PSDPing -ComputerName $ComputerName -ErrorAction Stop + Test-PSDAdminShare -ComputerName $ComputerName -ErrorAction Stop + Test-PSDWinRM -ComputerName $ComputerName -ErrorAction Stop + write-PSDInstallLog -Message "$ComputerName has passed all connection tests" + return $true + } + CATCH + { + write-PSDInstallLog -Message "$ComputerName failed a connection test." + return $false + } +} + +function Test-PSDPing +#Test ping for computer. +{ + [CmdletBinding()] + param + ( + [parameter(Mandatory = $true)] + [string]$ComputerName + ) + $PingTest = Test-Connection -ComputerName $ComputerName -BufferSize 8 -Count 1 -Quiet + If ($PingTest) + { + write-PSDInstallLog -Message "The Ping test for $ComputerName has PASSED" + } + Else + { + write-PSDInstallLog -Message "$ComputerName failed ping test" + throw [System.Net.NetworkInformation.PingException] "$ComputerName failed ping test." + } +} + +function Test-PSDAdminShare +#Test Conection to admin C$ share. +{ + [CmdletBinding()] + param + ( + [parameter(Mandatory = $true)] + [string]$ComputerName + ) + $AdminShare = "\\" + $ComputerName + "\C$" + $AdminAccess = Test-Path -Path $AdminShare -ErrorAction Stop + if ($AdminAccess) + { + write-PSDInstallLog -Message "The admin share connection test $ComputerName has PASSED" + } + Else + { + write-PSDInstallLog -Message "$ComputerName admin share not found" + throw [System.IO.FileNotFoundException] "$ComputerName admin share not found" + + } +} + +function Test-PSDWinRM +#Test WinRM. +{ + [CmdletBinding()] + param + ( + [parameter(Mandatory = $true)] + [string]$ComputerName + ) + Try + { + Test-WSMan -computername $ComputerName -ErrorAction Stop + write-PSDInstallLog -Message "The WINRM check for $ComputerName has PASSED" + } + Catch + { + throw [System.IO.DriveNotFoundException] "$ComputerName cannot be connected to via WINRM" + } +} + +function Test-PSDRoleInstalled +#Tests to see if a particular Windows feature/role is instaled +{ + [CmdletBinding()] + param + ( + [parameter(Mandatory = $true)] + [string]$RoleName, + [parameter()] + [string]$ComputerName = $ENV:COMPUTERNAME + ) + Try + { + write-PSDInstallLog -Message "Now confirming if the role is installed on the machine" + $FeatureInfo = Get-WindowsFeature -Name $RoleName -ComputerName $ComputerName -Verbose:$false + if($FeatureInfo.InstallState -eq $true) + { + write-PSDInstallLog -Message "The role is installed on the machine" + return $true + } + else + { + write-PSDInstallLog -Message "The role $($RoleName) is NOT installed on the machine" -LogLevel 2 + return $false + } + } + Catch + { + throw [System.IO.DriveNotFoundException] "An Error occured with detecting the roles installation state" + } +} + +Function Start-PSDLog +#Set global variable for the write-PSDInstallLog function in this session or script. +{ + [CmdletBinding()] + param ( + #[ValidateScript({ Split-Path $_ -Parent | Test-Path })] + [string]$FilePath + ) + try + { + if(!(Split-Path $FilePath -Parent | Test-Path)) + { + New-Item (Split-Path $FilePath -Parent) -Type Directory | Out-Null + } + #Confirm the provided destination for logging exists if it doesn't then create it. + if (!(Test-Path $FilePath)) + { + ## Create the log file destination if it doesn't exist. + New-Item $FilePath -Type File | Out-Null + } + ## Set the global variable to be used as the FilePath for all subsequent write-PSDInstallLog + ## calls in this session + $global:ScriptLogFilePath = $FilePath + } + catch + { + #In event of an error write an exception + Write-Error $_.Exception.Message + } +} + +Function Write-PSDInstallLog +#Write the log file if the global variable is set +{ + param ( + [Parameter(Mandatory = $true)] + [string]$Message, + [Parameter()] + [ValidateSet(1, 2, 3)] + [string]$LogLevel=1, + [Parameter(Mandatory = $false)] + [bool]$writetoscreen = $true + ) + $TimeGenerated = "$(Get-Date -Format HH:mm:ss).$((Get-Date).Millisecond)+000" + $Line = '' + $LineFormat = $Message, $TimeGenerated, (Get-Date -Format MM-dd-yyyy), "$($MyInvocation.ScriptName | Split-Path -Leaf):$($MyInvocation.ScriptLineNumber)", $LogLevel + $Line = $Line -f $LineFormat + [system.GC]::Collect() + Add-Content -Value $Line -Path $global:ScriptLogFilePath + if($writetoscreen) + { + switch ($LogLevel) + { + '1'{ + Write-Verbose -Message $Message + } + '2'{ + Write-Warning -Message $Message + } + '3'{ + Write-Error -Message $Message + } + Default { + } + } + } + if($writetolistbox -eq $true) + { + $result1.Items.Add("$Message") + } +} + +function set-PSDDefaultLogPath +{ + #Function to set the default log path if something is put in the field then it is sent somewhere else. + [CmdletBinding()] + param + ( + [parameter(Mandatory = $false)] + [bool]$defaultLogLocation = $true, + [parameter(Mandatory = $false)] + [string]$LogLocation + ) + if($defaultLogLocation) + { + $LogPath = Split-Path $script:MyInvocation.MyCommand.Path + $LogFile = "$($($script:MyInvocation.MyCommand.Name).Substring(0,$($script:MyInvocation.MyCommand.Name).Length-4)).log" + Start-PSDLog -FilePath $($LogPath + "\" + $LogFile) + } + else + { + $LogPath = $LogLocation + $LogFile = "$($($script:MyInvocation.MyCommand.Name).Substring(0,$($script:MyInvocation.MyCommand.Name).Length-4)).log" + Start-PSDLog -FilePath $($LogPath + "\" + $LogFile) + } +} + +function get-PSDNetworkConfiguration +{ + [CmdletBinding()] + param() + $Information = Get-NetIPConfiguration | Select-Object InterfaceAlias,IPv4Address,IPv6Address,NetProfile,DNSServer + $NetworkInfo = @() + ForEach($Interface in $Information) + { + $Hash = @{ + InterfaceName = $Interface.InterfaceAlias + IPv4Address = $Interface.IPv4Address.IPAddress + IPv6Address = $Interface.IPv6Address.IPAddress + NetProfile = $Interface.NetProfile.Name + DNSServer = $Interface.DNSServer.ServerAddresses + } + $Item = New-Object -TypeName psobject -Property $Hash + $NetworkInfo += $Item + } + return $NetworkInfo +} + +#endregion HelperFunctions +############################################ + +############################################ +#region ConfigurationFunctions +function invoke-IISConfiguration +{ + [CmdletBinding()] + param( + [parameter()] + [string]$psDeploymentFolder, + [parameter()] + [string]$psVirtualDirectory + ) + begin + { + if(!($TargetEqualsLocal)) + { + if(!(Test-PSDConnectivity -ComputerName $ComputerName)) + { + Write-PSDInstallLog -Message "A connection test failed validate which connnection test failed in the LogFile and remediate nothing was attempted to install at this time." -LogLevel 3 + break + } + } + if(!(Test-PSDRoleInstalled -RoleName "WEB-Server" -ComputerName $ComputerName) -or !(Test-PSDRoleInstalled -RoleName "WebDav-Redirector" -ComputerName $ComputerName)) + { + Write-PSDInstallLog -Message "The configuration attempt failed because a role was missing review the log for details" -LogLevel 3 + break + } + if(!(Test-Path -Path $psDeploymentFolder)) + { + Write-PSDInstallLog -Message "The deployment share doesn't exist re-run the script with a share that exists" -LogLevel 3 + break + } + } + process + { + # Confirm the Services are present and the start up configuration is properly configured + write-PSDInstallLog -Message "Confirming the WebDav services are configured properly on $($ComputerName)" + try + { + $Count = 0 + Do + { + $Count++ + Write-PSDInstallLog -Message "Attempt number $($Count) at connecting and starting the service" + $MRxDavserviceState = Get-Service -ComputerName $ComputerName -Name MRxDAV -ErrorAction SilentlyContinue + $WebClientServiceState = Get-Service -ComputerName $ComputerName -Name WebClient -ErrorAction SilentlyContinue + if(($MRxDavserviceState) -and $MRxDavserviceState.Status -ne "Running") + { + Write-PSDInstallLog -Message "We found $($MRxDAVServiceState.DisplayName) and are now attempting to set it to a running state" + Set-Service -ComputerName $ComputerName -StartupType Automatic -ErrorAction Stop -Status Running -Name $MRxDavserviceState.Name + } + if(($WebClientServiceState) -and $WebClientServiceState.Status -ne "Running") + { + Write-PSDInstallLog -Message "We found $($WebClientServiceState.DisplayName) and are now attempting to set it to a running state" + Set-Service -ComputerName $ComputerName -StartupType Automatic -ErrorAction Stop -Status Running -Name $WebClientServiceState.Name + } + elseif (!($MRxDavserviceState) -and !($WebClientServiceState)) { + Write-PSDInstallLog -Message "Neither service was found waiting 15 seconds" -LogLevel 2 + Start-Sleep -Seconds 15 + } + } + until((($MRxDavserviceState) -and ($WebClientServiceState)) -or ($Count -ge 5)) + if(!($MRxDavserviceState) -or !($WebClientServiceState)) + { + Write-PSDInstallLog -Message "Something went wrong with the installation, and the services are not appearing." + break + } + Write-PSDInstallLog -Message "Succesfully Completed starting the required services." + } + Catch + { + Write-PSDInstallLog -Message "Something went wrong with setting or starting the services refer to the log to validate." -LogLevel 3 + } + #Create the virtual directory + try + { + write-PSDInstallLog -Message "Now creating the Virtual Directory" + if(Test-Path -Path $psDeploymentFolder) + { + $DuplicateCheck = Get-WebVirtualDirectory -Name $psVirtualDirectory + if($DuplicateCheck) + { + Write-PSDInstallLog -Message "The website $($psVirtualDirectory) already exits" -LogLevel 3 + break + } + $VirtualDirectoryResults = New-WebVirtualDirectory -Site "Default Web Site" -Name "$($psVirtualDirectory)" -PhysicalPath $psDeploymentFolder + if($VirtualDirectoryResults) + { + Write-PSDInstallLog -Message "Succesfully created the Virtual Directory $($VirtualDirectoryResults.Name) this drive maps to $($VirtualDirectoryResults.PhysicalPath)" + } + Write-PSDInstallLog -Message "Now enabling WebDav" + Set-WebConfigurationProperty -pspath 'MACHINE/WEBROOT/APPHOST' -location "Default Web Site/" -filter "system.webServer/webdav/authoring" -name "enabled" -value "True" + $HERE = @" + set config "Default Web Site/$($psVirtualDirectory)" /section:system.webServer/webdav/authoringRules /+[users='*',path='*',access='Read,Source'] /commit:apphost +"@ + $Results = start-process C:\Windows\System32\inetsrv\AppCMD.EXE -ArgumentList $HERE -NoNewWindow -PassThru | Out-Null + Start-Sleep -Seconds 5 + if(!((Get-WebConfigurationProperty -PSPath "IIS:\Sites\Default Web Site\$($psVirtualDirectory)" -Filter "system.webServer/staticContent" -Name ".").Collection | Where-Object {$_.fileExtension -eq ".*"})) + { + Write-PSDInstallLog -Message "The Mime Type has not yet been added for virtual directories now adding..." + $MimeREsults = Add-WebConfigurationProperty -PSPath "IIS:\Sites\Default Web Site\$($psVirtualDirectory)" -Filter "system.webServer/staticContent" -Name "." -Value @{ fileExtension='.*'; mimeType='Text/Plain'} + if($MimeREsults) + { + Write-PSDInstallLog -Message "Succesfully created the Mime Type" + } + } + Write-PSDInstallLog -Message "Enabling the Directory browsing" + set-WebConfigurationProperty -filter /system.webServer/directoryBrowse -name enabled -PSPath "IIS:\Sites\Default Web Site\$($psVirtualDirectory)" -Value $true + Write-PSDInstallLog -Message "Directory browsing has been enabled for the $($psVirtualDirectory)" + <#if(!((Get-WebConfigurationProperty -Filter '/system.webServer/webdav/authoringRules' -Location "IIS:\Default Web Site\$($psVirtualDirectory)" -Name '.').Collection | Where-Object {$_.users -eq "*" -and $_.Path -eq "*" -and $_.access -eq "Read,Source"})) + { + Write-PSDInstallLog -Message "Enabling WebDav Authoring Rules" + $accessRule = @{ + users = '*' + path = '*' + access = 'Read,Source' + } + $WebDavAuthoring = Add-WebConfigurationProperty -Filter '/system.webServer/webdav/authoringRules' -Location "IIS:\Default Web Site\$($psVirtualDirectory)" -Name '.' -Value $accessRule + if($WebDavAuthoring) + { + Write-PSDInstallLog -Message "Configured WebDav Rules" + } + }#> + Write-PSDInstallLog -Message "Now configuring security settings for authentication" + #Written using ScriptGenerator from IIS + Set-WebConfigurationProperty -pspath 'MACHINE/WEBROOT/APPHOST' -location "Default Web Site/$($psVirtualDirectory)" -filter "system.webServer/security/authentication/anonymousAuthentication" -name "enabled" -value "False" + #Written Using Script Generator from IIS + Set-WebConfigurationProperty -pspath 'MACHINE/WEBROOT/APPHOST' -location "Default Web Site/$($psVirtualDirectory)" -filter "system.webServer/security/authentication/windowsAuthentication" -name "enabled" -value "True" + #Setting WebDav Settings + Write-PSDInstallLog -Message "Setting WEBDavSettings" + Write-PSDInstallLog -Message "Setting the Authoring rules for Default MimType" + Set-WebConfigurationProperty -pspath 'MACHINE/WEBROOT/APPHOST' -location "Default Web Site/" -filter "system.webServer/webdav/authoringRules" -name "defaultMimeType" -value "text/xml" + Write-PSDInstallLog -Message "Setting the Infinite Depth rules for the virtual directory" + Set-WebConfigurationProperty -pspath 'MACHINE/WEBROOT/APPHOST' -location "Default Web Site/$($psVirtualDirectory)" -filter "system.webServer/webdav/authoring/properties" -name "allowInfinitePropfindDepth" -value "True" + Write-PSDInstallLog -Message "Setting the the Infinite Depth rules for the Default Web Site" + Set-WebConfigurationProperty -pspath 'MACHINE/WEBROOT/APPHOST' -location "Default Web Site" -filter "system.webServer/webdav/authoring/properties" -name "allowInfinitePropfindDepth" -value "True" + Write-PSDInstallLog -Message "Turning off the apply to WebDav setting for File Extensions - Allows it to be configured or altered as needed" + Set-WebConfigurationProperty -pspath 'MACHINE/WEBROOT/APPHOST' -location "Default Web Site/$($psVirtualDirectory)" -filter "system.webServer/security/requestFiltering/fileExtensions" -name "applyToWebDAV" -value "false" + Set-WebConfigurationProperty -pspath 'MACHINE/WEBROOT/APPHOST' -location "Default Web Site/" -filter "system.webServer/security/requestFiltering/fileExtensions" -name "applyToWebDAV" -value "false" + Write-PSDInstallLog -Message "Turning off teh Request filtering for Verbs on WebDav" + Set-WebConfigurationProperty -pspath 'MACHINE/WEBROOT/APPHOST' -location "Default Web Site/$($psVirtualDirectory)" -filter "system.webServer/security/requestFiltering/verbs" -name "applyToWebDav" -value "False" + Set-WebConfigurationProperty -pspath 'MACHINE/WEBROOT/APPHOST' -location "Default Web Site/" -filter "system.webServer/security/requestFiltering/verbs" -name "applyToWebDav" -value "False" + $hiddenSegments = Get-IISConfigSection -SectionPath 'system.webServer/security/requestFiltering' | Get-IISConfigElement -ChildElementName 'hiddenSegments' + Set-IISConfigAttributeValue -ConfigElement $hiddenSegments -AttributeName 'applyToWebDAV' -AttributeValue $false + } + if(!(Test-Path -Path $psDeploymentFolder)) + { + invoke-IISConfiguration + } + + } + Catch + { + Write-PSDInstallLog -Message "Something went wrong" -LogLevel 3 + } + } +} +#Endregion ConfigurationFunctions +############################################ + + +} + +process +{ + ############################################ + #region StartUpChecks + set-PSDDefaultLogPath + #Block any unallowed action + if(($Install -and $Configure) -and !($Allowreboot)) + { + Write-PSDInstallLog -Message "Error - you canot run the install and configure command together without allowing a reboot" -LogLevel 3 + break + } + + if($ENV:COMPUTERNAME -eq $ComputerName) + { + $TargetEqualsLocal = $True + } + + #Start Time Calculation + $StartTime = Get-Date + + #endregion StartUpChecks + ############################################ + + ############################################ + #region GatherActions + Write-PSDInstallLog -Message "The Script is currently running on $($ENV:COMPUTERNAME) and was targeted to run against $($ComputerName)" + Write-PSDInstallLog -Message "Upon completion several roles will be installed upon $($ComputerName)" + Write-PSDInstallLog -Message "Completed initilization of all pre-written functions. Now documenting running environment" + Write-PSDInstallLog -Message "The Script was executed with commands: $($MyInvocation.Line)" + Write-PSDInstallLog -Message "The Current running computer is: $($ENV:COMPUTERNAME.ToUpper())" + Write-PSDInstallLog -Message "The Current user is $($ENV:USERNAME) and is an administrator" + $NetworkSettings = get-PSDNetworkConfiguration + Write-PSDInstallLog -Message "The EXECUTING SERVER $($ENV:COMPUTERNAME.ToUpper()) Network information is:" + foreach($Setting in $NetworkSettings) + { + Write-PSDInstallLog -Message "$($ENV:COMPUTERNAME.ToUpper()) - Profile - $($Setting.NetProfile)" + Write-PSDInstallLog -Message "$($ENV:COMPUTERNAME.ToUpper()) - IPv4 Ad - $($Setting.IPv4Address)" + Write-PSDInstallLog -Message "$($ENV:COMPUTERNAME.ToUpper()) - IPv6 AD - $($Setting.IPv6Address)" + Write-PSDInstallLog -Message "$($ENV:COMPUTERNAME.ToUpper()) - DNS Ser - $($Setting.DNSServer)" + } + ##check if the target computer is remote if it IS remote then and only then check the connection + if(!($TargetEqualsLocal)) + { + Write-PSDInstallLog -Message "Now validating that we can connect to the target server $($ComputerName)" + if(!(Test-PSDConnectivity -ComputerName $ComputerName)) + { + Write-PSDInstallLog -Message "Cannot reach the target computer $($ComputerName) now exiting" -LogLevel 3 + break + } + $TargetServerNetworkSettings = (Invoke-Command -HideComputerName -ComputerName $ComputerName -ScriptBlock ${Function:get-PSDNetworkConfiguration}) + Write-PSDInstallLog -Message "The TARGET SERVER $($COMPUTERNAME.ToUpper()) Network information is:" + foreach($Setting in $TargetServerNetworkSettings) + { + Write-PSDInstallLog -Message "$($COMPUTERNAME.ToUpper()) - Profile - $($Setting.NetProfile)" + Write-PSDInstallLog -Message "$($COMPUTERNAME.ToUpper()) - IPv4 Ad - $($Setting.IPv4Address)" + Write-PSDInstallLog -Message "$($COMPUTERNAME.ToUpper()) - IPv6 AD - $($Setting.IPv6Address)" + Write-PSDInstallLog -Message "$($COMPUTERNAME.ToUpper()) - DNS Ser - $($Setting.DNSServer)" + } + } + #endregion GatherActions + ############################################ + + ############################################ + #region InstallActions + if($Install) + { + if(!($TargetEqualsLocal)) + { + if(!(Test-PSDConnectivity -ComputerName $ComputerName) -or (Test-PSDRoleInstalled -RoleName "WEB-Server" -ComputerName $ComputerName)) + { + Write-PSDInstallLog -Message "A connection test failed validate which connnection test failed in the LogFile and remediate nothing was attempted to install at this time." -LogLevel 3 + if(Test-PSDRoleInstalled -RoleName "WEB-Server" -ComputerName $ComputerName) + { + Write-PSDInstallLog -Message "The installation failed because IIS was already installed and we don't want to break an existing installation" -LogLevel 3 + } + break + } + } + if($TargetEqualsLocal) + { + if(Test-PSDRoleInstalled -RoleName "WEB-Server" -ComputerName $ComputerName) + { + Write-PSDInstallLog -Message "The installation failed because IIS was already installed and we don't want to break an existing installation" -LogLevel 3 + break + } + } + write-PSDInstallLog -Message "The server is available and does NOT have IIS installed. Now preparing to install IIS" + try + { + $IISResults = Install-WindowsFeature -Name Web-Server -ComputerName $ComputerName -Verbose:$false + if($IISResults.Success) + { + write-PSDInstallLog -Message "Succesfully installed the IIS install with Exit Code $($IISResults.ExitCode) and Value $($IISResults.ExitCode.Value__)" + } + Write-PSDInstallLog -Message "Now attempting to install other required IIS Features" + #ToDO make this a proper hash table/list to explain info next to it at the end and evaluate or to allow install ALL sub features + $Featurelist = @("Web-Custom-Logging","Web-Log-Libraries","Web-Request-Monitor","Web-Http-Tracing","Web-Security","Web-Filtering","Web-Basic-Auth","Web-Digest-Auth","Web-Url-Auth","Web-Windows-Auth","Web-Mgmt-Console","Web-Metabase","Web-Common-Http","Web-Default-Doc","Web-Dir-Browsing","Web-Http-Errors","Web-Static-Content","Web-Http-Redirect","Web-DAV-Publishing") + $FeatureResults = Install-WindowsFeature -Name $Featurelist -ComputerName $ComputerName + write-PSDInstallLog -Message "Installed the Common HTTP Features with Exit Code $($FeatureResults.ExitCode) and Value $($FeatureResults.ExitCode.Value__)" + <# + This section has been blocked out. It is INCREDIBLY time consuming to install each feature one at a time however may add this back in as an option later on as an "advanced" install option + for people who want to have each step logged and are willing to wait the extra time when figuring out if a specific feature install is causing an issue. + foreach($Feature in $Featurelist){ + Write-PSDInstallLog -Message "Now attempting to install $($Feature)" + $FeatureResults = $null + $FeatureResults = Install-WindowsFeature -Name $Feature -ComputerName $ComputerName -Verbose:$false + write-PSDInstallLog -Message "Installed the $Feature with Exit Code $($FeatureResults.ExitCode) and Value $($FeatureResults.ExitCode.Value__)" + } + #> + $WEBDAVResults = Install-WindowsFeature -Name "WebDav-Redirector" -ComputerName $ComputerName -Verbose:$false + if($WEBDAVResults.Success) + { + write-PSDInstallLog -Message "Completed the installation of the WEBDAV-FEature on $($ComputerName) and Value $($WEBDAVResults.ExitCode.Value__)" + if($WEBDAVResults.ExitCode.Value__ -eq "3010"){ + write-PSDInstallLog -Message "The server $($ComputerName) requires a reboot to finalize the WebDav installation" + if($Allowreboot) + { + Write-PSDInstallLog -Message "A Reboot for $ComputerName was approved at the run of the script" + if($TargetEqualsLocal) + { + Write-PSDInstallLog -Message "Validated that the computer running the script $($ENV:COMPUTERNAME) IS the same as the target computer $($ComputerName)" -LogLevel 2 + Restart-Computer -ComputerName $ComputerName -Force + } + if($ENV:COMPUTERNAME -ne $ComputerName) + { + Write-PSDInstallLog -Message "Validated that the computer running the script $($ENV:COMPUTERNAME) is NOT the same as the target computer $($ComputerName)" -LogLevel 2 + Restart-Computer -ComputerName $ComputerName -Wait -Force + if($Configure){ + $ConfigureResults = (Invoke-Command -HideComputerName -ComputerName $ComputerName -ScriptBlock ${Function:invoke-IISConfiguration}) + Write-PSDInstallLog -Message "Completed the process for all selected flags now exiting" + break + } + } + } + elseif (!($AllowReboot)) + { + Write-PSDInstallLog -Message "The server $($Computername) was NOT allowed to reboot. You must reboot the server and re-run the script with the -Configure Flag" -LogLevel 2 + } + } + } + } + catch + { + write-PSDInstallLog -Message "Something went wrong on line $($_.Exception.InvocationInfo.ScriptLineNumber) the error message was: $($_.Exception.Message)" -LogLevel 3 + } + } + + #endregion InstallActions + ############################################ + + ############################################ + #region ConfigureActions + + if($Configure) + { + if($ComputerName -ne $ENV:COMPUTERNAME) + { + $ConfigureResults = (Invoke-Command -HideComputerName -ComputerName $ComputerName -ScriptBlock ${Function:invoke-IISConfiguration}) + Write-PSDInstallLog -Message "Completed the process for all selected flags now exiting" + } + else + { + if(($psDeploymentFolder) -and !($psVirtualDirectory)) + { + Write-PSDInstallLog -Message "Now configuring the WebDAV and IIS for the MDT Share at $($psDeploymentFolder)" + invoke-IISConfiguration -psDeploymentFolder $psDeploymentFolder + Write-PSDInstallLog -Message "Completed the configuration" + } + elseif (($psDeploymentFolder) -and ($psVirtualDirectory)) + { + Write-PSDInstallLog -Message "Now configuring the WebDAV and IIS for the MDT share at $($psDeploymentFolder) with $($PSWebsite)" + invoke-IISConfiguration -psDeploymentFolder $psDeploymentFolder -psVirtualDirectory $psVirtualDirectory + } + + } + } + #endregion ConfigureActions + ############################################ + + ############################################ + #region ShutdownChecks + $EndTime = Get-Date + $Duration = New-TimeSpan -Start $StartTime -End $EndTime + Write-PSDInstallLog -Message "The script has completed running and took $($Duration.Hours) Hours and $($Duration.Minutes) Minutes and $($Duration.Seconds) seconds" + #endregion ShutdownChecks + ############################################ +} diff --git a/Installer/PSD-Install-IIS.ps1 b/Installer/PSD-Install-IIS.ps1 deleted file mode 100644 index fd179b1..0000000 --- a/Installer/PSD-Install-IIS.ps1 +++ /dev/null @@ -1,3 +0,0 @@ -#PSD-Install-IIS.ps1 -#installs and configures IIS and WebDAV for PSD/MDT -#Placeholder \ No newline at end of file diff --git a/PSD.pssproj b/PSD.pssproj deleted file mode 100644 index 4e40897..0000000 --- a/PSD.pssproj +++ /dev/null @@ -1,62 +0,0 @@ - - - - Debug - 2.0 - 6CAFC0C6-A428-4d30-A9F9-700E829FEA51 - Exe - MyApplication - MyApplication - PSD - - - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/PSD.sln b/PSD.sln deleted file mode 100644 index a86ead3..0000000 --- a/PSD.sln +++ /dev/null @@ -1,22 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 14 -VisualStudioVersion = 14.0.25123.0 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{F5034706-568F-408A-B7B3-4D38C6DB8A32}") = "PSD", "PSD.pssproj", "{6CAFC0C6-A428-4D30-A9F9-700E829FEA51}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {6CAFC0C6-A428-4D30-A9F9-700E829FEA51}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6CAFC0C6-A428-4D30-A9F9-700E829FEA51}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6CAFC0C6-A428-4D30-A9F9-700E829FEA51}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6CAFC0C6-A428-4D30-A9F9-700E829FEA51}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection -EndGlobal diff --git a/ReadMe.md b/ReadMe.md index a7ecc42..431f08b 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -19,10 +19,5 @@ Supported deployment scenarios include deployment from the following content rep PSD is very much a work-in-progress solution, so stay tuned as we rapidly move forward on this. -## Visual Studio Notes -The solution file for Visual Studio requires PowerShell for Visual Studio (3rd party extension for Visual Studio 2015). - -When using Visual Studio to edit the PowerShell scripts, configure the editor to use spaces instead of tabs for PowerShell. - ## Related References (intentionally blank) \ No newline at end of file diff --git a/Scripts/PSDApplications.ps1 b/Scripts/PSDApplications.ps1 index 1a9bef7..d86cebf 100644 --- a/Scripts/PSDApplications.ps1 +++ b/Scripts/PSDApplications.ps1 @@ -9,12 +9,21 @@ # // # // *************************************************************************** -# Load core modules +param ( + +) +# Load core modules Import-Module PSDUtility Import-Module PSDDeploymentShare + $verbosePreference = "Continue" +#Write-Verbose -Message "$($MyInvocation.MyCommand.Name): Load core modules" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Load core modules" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Deployroot is now $($tsenv:DeployRoot)" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): env:PSModulePath is now $env:PSModulePath" + # Internal functions # Function to install a specified app @@ -25,45 +34,76 @@ function Install-PSDApplication [string] $id ) + # Make sure we access to the application folder + #Write-Verbose "$($MyInvocation.MyCommand.Name): Make sure the app exists" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Make sure we access to the application folder" + if ((Test-Path "DeploymentShare:\Applications") -ne $true) + { + + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): no access to DeploymentShare:\Applications , skipping." + return 0 + } + # Make sure the app exists - if ((Test-Path DeploymentShare:\Applications\$id) -ne $true) + #Write-Verbose "$($MyInvocation.MyCommand.Name): Make sure the app exists" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Make sure the app exists DeploymentShare:\Applications\$id" + if ((Test-Path "DeploymentShare:\Applications\$id") -ne $true) { - Write-Verbose "Unable to find application $id, skipping." + + #Write-Verbose "$($MyInvocation.MyCommand.Name): Unable to find application $id, skipping." + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Unable to find application $id, skipping." return 0 } # Get the app - $app = Get-Item DeploymentShare:\Applications\$id - Write-Verbose "Processing $($app.Name)" + #Write-Verbose "$($MyInvocation.MyCommand.Name): Get the app" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Get the app" + $app = Get-Item "DeploymentShare:\Applications\$id" + + #Write-Verbose "$($MyInvocation.MyCommand.Name): Processing $($app.Name)" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Processing :$($app.Name)" # Process dependencies (recursive) + #Write-Verbose "$($MyInvocation.MyCommand.Name): Process dependencies (recursive)" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Process dependencies (recursive)" if ($app.Dependency.Count -ne 0) { $app.Dependency | Install-PSDApplication } # Check if the app has been installed already + #Write-Verbose "$($MyInvocation.MyCommand.Name): Check if the app has been installed already" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Check if the app has been installed already" $alreadyInstalled = @() $alreadyInstalled = @((Get-Item tsenvlist:InstalledApplications).Value) $found = $false $alreadyInstalled | ? {$_ -eq $id} | % {$found = $true} if ($found) { - Write-Verbose "Application $($app.Name) is already installed, skipping." + #Write-Verbose "$($MyInvocation.MyCommand.Name): Application $($app.Name) is already installed, skipping." + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Application $($app.Name) is already installed, skipping." return } # TODO: Check supported platforms + #Write-Verbose "$($MyInvocation.MyCommand.Name): TODO: Check supported platforms" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): TODO: Check supported platforms" # TODO: Check for uninstall string + #Write-Verbose "$($MyInvocation.MyCommand.Name): TODO: Check for uninstall string" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): TODO: Check for uninstall string" # Set the working directory + #Write-Verbose "$($MyInvocation.MyCommand.Name): Set the working directory" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Set the working directory" $workingDir = "" if ($app.WorkingDirectory -ne "" -and $app.WorkingDirectory -ne ".") { if ($app.WorkingDirectory -like ".\*") { # App content is on the deployment share, get it + #Write-Verbose "$($MyInvocation.MyCommand.Name): App content is on the deployment share, get it" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): App content is on the deployment share, get it" $appContent = Get-PSDContent -Content "$($app.WorkingDirectory.Substring(2))" $workingDir = $appContent } @@ -72,14 +112,21 @@ function Install-PSDApplication $workingDir = $app.WorkingDirectory } # TODO: Substitute + #Write-Verbose "$($MyInvocation.MyCommand.Name): TODO: Substitute" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): TODO: Substitute" # TODO: Validate connection + #Write-Verbose "$($MyInvocation.MyCommand.Name): TODO: Validate connection" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): TODO: Validate connection" } # Install the app + #Write-Verbose "$($MyInvocation.MyCommand.Name): Install the app" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Install the app" if ($app.CommandLine -eq "") { - Write-Verbose "No command line specified (bundle)." + #Write-Verbose "$($MyInvocation.MyCommand.Name): No command line specified (bundle)." + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): No command line specified (bundle)." } elseif ($app.CommandLine -ilike "install-package *") { @@ -88,30 +135,43 @@ function Install-PSDApplication elseif ($app.CommandLine -icontains ".appx" -or $app.CommandLine -icontains ".appxbundle") { # TODO: Process modern app + #Write-Verbose "$($MyInvocation.MyCommand.Name): TODO: Process modern app" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): TODO: Process modern app" } else { $cmd = $app.CommandLine # TODO: Substitute - Write-Verbose "About to run: $cmd" + #Write-Verbose "$($MyInvocation.MyCommand.Name): TODO: Substitute" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): TODO: Substitute" + #Write-Verbose "$($MyInvocation.MyCommand.Name): About to run: $cmd" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): About to run: $cmd" if ($workingDir -eq "") { $result = Start-Process -FilePath "$toolRoot\bddrun.exe" -ArgumentList $cmd -Wait -Passthru } else { - Write-Verbose "Setting working directory to $workingDir" + #Write-Verbose "$($MyInvocation.MyCommand.Name): Setting working directory to $workingDir" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Setting working directory to $workingDir" $result = Start-Process -FilePath "$toolRoot\bddrun.exe" -ArgumentList $cmd -WorkingDirectory $workingDir -Wait -Passthru } # TODO: Check return codes - Write-Verbose "Application $($app.Name) return code = $($result.ExitCode)" + #Write-Verbose "$($MyInvocation.MyCommand.Name): TODO: Check return codes" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): TODO: Check return codes" + #Write-Verbose "$($MyInvocation.MyCommand.Name): Application $($app.Name) return code = $($result.ExitCode)" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Application $($app.Name) return code = $($result.ExitCode)" } # Update list of installed apps + #Write-Verbose "$($MyInvocation.MyCommand.Name): Update list of installed apps" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Update list of installed apps" $alreadyInstalled += $id $tsenvlist:InstalledApplications = $alreadyInstalled # Reboot if specified + #Write-Verbose "$($MyInvocation.MyCommand.Name): Reboot if specified" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Reboot if specified" if ($app.Reboot -ieq "TRUE") { return 3010 @@ -124,27 +184,54 @@ function Install-PSDApplication # Main code +#Write-Verbose "$($MyInvocation.MyCommand.Name): Main code" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Main code" # Get tools +#Write-Verbose "$($MyInvocation.MyCommand.Name): Get tools" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Get tools" $toolRoot = Get-PSDContent "Tools\$($tsenv:Architecture)" + +# Single application install initiated by a Task Sequence action +# Note: The ApplicationGUID variable isn’t set globally. It’s set only within the scope of the Install Application action/step. One of the hidden mysteries of the task sequence engine :) + +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Checking for single application install step" +If ($tsenv:ApplicationGUID -ne "") { + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Mandatory Single Application install indicated. Guid is $($tsenv:ApplicationGUID)" + Install-PSDApplication $tsenv:ApplicationGUID + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Mandatory Single Application installed, exiting application step" + Exit +} +else +{ + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): No Single Application install found. Continue with checking for dynamic applications" +} + + # Process applications +#Write-Verbose "$($MyInvocation.MyCommand.Name): Process applications" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Process applications" if ($tsenvlist:MandatoryApplications.Count -ne 0) { - Write-Verbose "Processing $($tsenvlist:MandatoryApplications.Count) mandatory applications." + #Write-Verbose "$($MyInvocation.MyCommand.Name): Processing $($tsenvlist:MandatoryApplications.Count) mandatory applications." + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Processing $($tsenvlist:MandatoryApplications.Count) mandatory applications." $tsenvlist:MandatoryApplications | Install-PSDApplication } else { - Write-Verbose "No mandatory applications specified." + #Write-Verbose "$($MyInvocation.MyCommand.Name): No mandatory applications specified." + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): No mandatory applications specified." } if ($tsenvlist:Applications.Count -ne 0) { - Write-Verbose "Processing $($tsenvlist:Applications.Count) applications." + #Write-Verbose "$($MyInvocation.MyCommand.Name): Processing $($tsenvlist:Applications.Count) applications." + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Processing $($tsenvlist:Applications.Count) applications." $tsenvlist:Applications | % { Install-PSDApplication $_ } } else { - Write-Verbose "No applications specified." + #Write-Verbose "$($MyInvocation.MyCommand.Name): No applications specified." + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): No applications specified." } diff --git a/Scripts/PSDApplyOS.ps1 b/Scripts/PSDApplyOS.ps1 index 29b7fc1..a58c1a3 100644 --- a/Scripts/PSDApplyOS.ps1 +++ b/Scripts/PSDApplyOS.ps1 @@ -6,37 +6,97 @@ # // # // Purpose: Apply the specified operating system. # // +# // # // *************************************************************************** +param ( + +) + # Load core modules +Import-Module Microsoft.BDD.TaskSequenceModule -Scope Global Import-Module DISM Import-Module PSDUtility Import-Module PSDDeploymentShare + $verbosePreference = "Continue" +#Write-Verbose -Message "$($MyInvocation.MyCommand.Name): Load core modules" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Load core modules" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Deployroot is now $($tsenv:DeployRoot)" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): env:PSModulePath is now $env:PSModulePath" + # Make sure we run at full power +#Write-Verbose -Message "$($MyInvocation.MyCommand.Name): Make sure we run at full power" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Make sure we run at full power" & powercfg.exe /s 8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c # Get the OS image details -Write-Verbose "Operating system: $($tsenv:OSGUID)" +#Write-Verbose -Message "$($MyInvocation.MyCommand.Name): Get the OS image details" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Get the OS image details" +#Write-Verbose -Message "$($MyInvocation.MyCommand.Name): Operating system: $($tsenv:OSGUID)" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Operating system: $($tsenv:OSGUID)" $os = Get-Item "DeploymentShare:\Operating Systems\$($tsenv:OSGUID)" $osSource = Get-PSDContent "$($os.Source.Substring(2))" $image = "$osSource$($os.ImageFile.Substring($os.Source.Length))" $index = $os.ImageIndex +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): os is now $os" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): osSource is now $osSource" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): image is now $image" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): index is now $index" + +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Verifying access to $image" +if((Test-Path -Path $image) -ne $true) +{ + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Unable to continue, could not access the WIM $image" + Show-PSDInfo -Message "Unable to continue, could not access the WIM $image" -Severity Error + Exit 1 +} + # Create a local scratch folder +#Write-Verbose -Message "$($MyInvocation.MyCommand.Name): Create a local scratch folder" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Create a local scratch folder" $scratchPath = "$(Get-PSDLocalDataPath)\Scratch" Initialize-PSDFolder $scratchPath # Apply the image -Write-Verbose "Applying image $image index $index to $($tsenv:OSVolume)" +#Write-Verbose -Message "$($MyInvocation.MyCommand.Name): Apply the image" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Apply the image" +#Write-Verbose -Message "$($MyInvocation.MyCommand.Name): Applying image $image index $index to $($tsenv:OSVolume)" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Applying image $image index $index to $($tsenv:OSVolume)" $startTime = Get-Date Expand-WindowsImage -ImagePath $image -Index $index -ApplyPath "$($tsenv:OSVolume):\" -ScratchDirectory $scratchPath $duration = $(Get-Date) - $startTime -Write-Verbose "Time to apply image: $($duration.ToString('hh\:mm\:ss'))" +#Write-Verbose -Message "$($MyInvocation.MyCommand.Name): Time to apply image: $($duration.ToString('hh\:mm\:ss'))" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Time to apply image: $($duration.ToString('hh\:mm\:ss'))" + +# Inject drivers using DISM if Setup.exe is missing +#$ImageFolder = $image | Split-Path | Split-Path +#Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Checking if Setup.exe is present in $ImageFolder" +#if(!(Test-Path -Path "$ImageFolder\Setup.exe")) +#{ +# Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Could not find Setup.exe, applying Unattend.xml (Use-WindowsUnattend)" +# if(Test-Path -Path "$($tsenv:OSVolume):\Windows\Panther\Unattend.xml") +# { +# Use-WindowsUnattend -Path "$($tsenv:OSVolume):\" -UnattendPath "$($tsenv:OSVolume):\Windows\Panther\Unattend.xml" -ScratchDirectory $scratchPath +# } +# else +# { +# Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Could not $($tsenv:OSVolume):\Windows\Panther\Unattend.xml" +# } +# +#} +#else +#{ +# Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Found Setup.exe, no need to apply Unattend.xml" +#} # Make the OS bootable -Write-Verbose "Configuring volume $($tsenv:BootVolume) to boot $($tsenv:OSVolume):\Windows." +#Write-Verbose -Message "$($MyInvocation.MyCommand.Name): Make the OS bootable" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Make the OS bootable" +#Write-Verbose -Message "$($MyInvocation.MyCommand.Name): Configuring volume $($tsenv:BootVolume) to boot $($tsenv:OSVolume):\Windows." +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Configuring volume $($tsenv:BootVolume) to boot $($tsenv:OSVolume):\Windows." if ($tsenv:IsUEFI -eq "True") { $args = @("$($tsenv:OSVolume):\Windows", "/s", "$($tsenv:BootVolume):", "/f", "uefi") @@ -45,16 +105,37 @@ else { $args = @("$($tsenv:OSVolume):\Windows", "/s", "$($tsenv:BootVolume):") } +#Added for troubleshooting (admminy) +#Write-Verbose -Message "$($MyInvocation.MyCommand.Name): Running bcdboot.exe with the following arguments: $args" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Running bcdboot.exe with the following arguments: $args" + $result = Start-Process -FilePath "bcdboot.exe" -ArgumentList $args -Wait -Passthru -Write-Verbose "BCDBoot completed, rc = $($result.ExitCode)" +#Write-Verbose -Message "$($MyInvocation.MyCommand.Name): BCDBoot completed, rc = $($result.ExitCode)" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): BCDBoot completed, rc = $($result.ExitCode)" # Fix the EFI partition type +#Write-Verbose -Message "$($MyInvocation.MyCommand.Name): Fix the EFI partition type if using UEFI" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Fix the EFI partition type if using UEFI" if ($tsenv:IsUEFI -eq "True") { # Fix the EFI partition type + #Write-Verbose -Message "$($MyInvocation.MyCommand.Name): Fix the EFI partition type" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Fix the EFI partition type" @" select volume $($tsenv:BootVolume) set id=c12a7328-f81f-11d2-ba4b-00a0c93ec93b exit "@ | diskpart } + +# Fix the recovery partition type for MBR disks, using diskpart.exe since the PowerShell cmdlets are currently missing some options (like ID for MBR disks) +if ($tsenv:IsUEFI -eq "False") +{ + # Fix the recovery partition type + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Fix the recovery partition type" + @" +select volume $($tsenv:RecoveryVolume) +set id=27 override +exit +"@ | diskpart +} \ No newline at end of file diff --git a/Scripts/PSDApplyWinPE.ps1 b/Scripts/PSDApplyWinPE.ps1 new file mode 100644 index 0000000..8bae13c --- /dev/null +++ b/Scripts/PSDApplyWinPE.ps1 @@ -0,0 +1,22 @@ +# // *************************************************************************** +# // +# // PowerShell Deployment for MDT +# // +# // File: PSDTemplate.ps1 +# // +# // Purpose: Apply the specified operating system. +# // +# // +# // *************************************************************************** + +param ( + +) + +# Load core modules +Import-Module PSDUtility +Import-Module PSDDeploymentShare +$verbosePreference = "Continue" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Load core modules" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Deployroot is now $deployRoot" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): env:PSModulePath is now $env:PSModulePath" diff --git a/Scripts/PSDConfigure.ps1 b/Scripts/PSDConfigure.ps1 index b8f73df..b9bb3f1 100644 --- a/Scripts/PSDConfigure.ps1 +++ b/Scripts/PSDConfigure.ps1 @@ -6,16 +6,29 @@ # // # // Purpose: Configure the unattend.xml to be used with the new OS. # // +# // # // *************************************************************************** -# Load core modules +param ( + +) +# Load core modules +Import-Module Microsoft.BDD.TaskSequenceModule -Scope Global Import-Module DISM Import-Module PSDUtility Import-Module PSDDeploymentShare + $verbosePreference = "Continue" +#Write-Verbose -Message "$($MyInvocation.MyCommand.Name): Load core modules" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Load core modules" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Deployroot is now $($tsenv:DeployRoot)" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): env:PSModulePath is now $env:PSModulePath" + # Load the unattend.xml +#Write-Verbose -Message "$($MyInvocation.MyCommand.Name): Load the unattend.xml" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Load the unattend.xml" $tsInfo = Get-PSDContent "Control\$($tsenv:TaskSequenceID)" [xml] $unattend = Get-Content "$tsInfo\unattend.xml" $namespaces = @{unattend='urn:schemas-microsoft-com:unattend'} @@ -23,9 +36,9 @@ $changed = $false $unattendXml = "$($tsenv:OSVolume):\Windows\Panther\Unattend.xml" Initialize-PSDFolder "$($tsenv:OSVolume):\Windows\Panther" - # Substitute the values in the unattend.xml - +#Write-Verbose -Message "$($MyInvocation.MyCommand.Name): Substitute the values in the unattend.xml" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Substitute the values in the unattend.xml" $scripts = Get-PSDContent "Scripts" [xml] $config = Get-Content "$scripts\ZTIConfigure.xml" $config | Select-Xml "//mapping[@type='xml']" | % { @@ -47,64 +60,86 @@ $config | Select-Xml "//mapping[@type='xml']" | % { { # Remove the node $_.Node.parentNode.removeChild($_.Node) | Out-Null - Write-Verbose "Removed $xpath from unattend.xml because the value was blank." + #Write-Verbose -Message "$($MyInvocation.MyCommand.Name): Removed $xpath from unattend.xml because the value was blank." + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Removed $xpath from unattend.xml because the value was blank." $changed = $true } elseif ($value -eq "" -and $prev -eq "" -and $removeIfBlank -eq "Parent") { # Remove the node $_.Node.parentNode.parentNode.removeChild($_.Node.parentNode) | Out-Null - Write-Verbose "Removed parent of $xpath from unattend.xml because the value was blank." + #Write-Verbose -Message "$($MyInvocation.MyCommand.Name): Removed parent of $xpath from unattend.xml because the value was blank." + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Removed parent of $xpath from unattend.xml because the value was blank." $changed = $true } elseif ($value -ne "") { # Set the new value $_.Node.InnerText = $value - Write-Verbose "Updated unattend.xml with $variable = $value (value was $prev)." + #Write-Verbose -Message "$($MyInvocation.MyCommand.Name): Updated unattend.xml with $variable = $value (value was $prev)." + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Updated unattend.xml with $variable = $value (value was $prev)." $changed = $true # See if this has a parallel "PlainText" entry, and if it does, set it to true $_.Node.parentNode | Select-Xml -XPath "unattend:PlainText" -Namespace $namespaces | % { $_.Node.InnerText = "true" - Write-Verbose "Updated PlainText entry to true." + #Write-Verbose -Message "$($MyInvocation.MyCommand.Name): Updated PlainText entry to true." + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Updated PlainText entry to true." } # Remove any contradictory entries $removes | % { $removeXpath = $_.Node.'#cdata-section' - Write-Verbose "*** $removeXpath" + #Write-Verbose -Message "$($MyInvocation.MyCommand.Name): *** $removeXpath" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): *** $removeXpath" $unattend | Select-Xml -XPath $removeXpath -Namespace $namespaces | % { $_.Node.parentNode.removeChild($_.Node) | Out-Null - Write-Verbose "Removed $removeXpath entry from unattend.xml." + #Write-Verbose -Message "$($MyInvocation.MyCommand.Name): Removed $removeXpath entry from unattend.xml." + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Removed $removeXpath entry from unattend.xml." } } } else { - Write-Verbose "No value found for $variable." + #Write-Verbose -Message "$($MyInvocation.MyCommand.Name): No value found for $variable." + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): No value found for $variable." } } } } # Save the file +#Write-Verbose -Message "$($MyInvocation.MyCommand.Name): Save the file" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Save the file" $unattend.Save($unattendXml) -Write-Verbose "Saved $unattendXml." +#Write-Verbose -Message "$($MyInvocation.MyCommand.Name): Saved $unattendXml." +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Saved $unattendXml." # TODO: Copy patches +#Write-Verbose -Message "$($MyInvocation.MyCommand.Name): TODO: Copy patches" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): TODO: Copy patches" # Apply the unattend.xml -Write-Verbose "Applying $unattendxml." +#Write-Verbose -Message "$($MyInvocation.MyCommand.Name): Apply the unattend.xml" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Apply the unattend.xml" $scratchPath = "$(Get-PSDLocalDataPath)\Scratch" Initialize-PSDFolder $scratchPath Use-WindowsUnattend -UnattendPath $unattendXml -Path "$($tsenv:OSVolume):\" -ScratchDirectory $scratchPath -NoRestart # Copy needed script and module files +#Write-Verbose -Message "$($MyInvocation.MyCommand.Name): Copy needed script and module files" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Copy needed script and module files" Initialize-PSDFolder "$($tsenv:OSVolume):\MININT\Scripts" $modules = Get-PSDContent "Tools\Modules" Copy-Item "$scripts\PSDStart.ps1" "$($tsenv:OSVolume):\MININT\Scripts" Copy-PSDFolder "$modules" "$($tsenv:OSVolume):\MININT\Tools\Modules" +# Save all the current variables for later use +#Write-Verbose -Message "$($MyInvocation.MyCommand.Name): Save all the current variables for later use" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Save all the current variables for later use" +Save-PSDVariables + # Request a reboot +#Write-Verbose -Message "$($MyInvocation.MyCommand.Name): Request a reboot" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Request a reboot" $tsenv:SMSTSRebootRequested = "true" diff --git a/Scripts/PSDCustomPostWU.ps1 b/Scripts/PSDCustomPostWU.ps1 new file mode 100644 index 0000000..cf428f7 --- /dev/null +++ b/Scripts/PSDCustomPostWU.ps1 @@ -0,0 +1,24 @@ +# // *************************************************************************** +# // +# // PowerShell Deployment for MDT +# // +# // File: PSDTemplate.ps1 +# // +# // Purpose: Apply the specified operating system. +# // +# // +# // *************************************************************************** + +param ( + +) + +# Load core modules +Import-Module Microsoft.BDD.TaskSequenceModule -Scope Global +Import-Module PSDUtility + +$verbosePreference = "Continue" + +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Load core modules" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Deployroot is now $($tsenv:DeployRoot)" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): env:PSModulePath is now $env:PSModulePath" diff --git a/Scripts/PSDCustomPreWU.ps1 b/Scripts/PSDCustomPreWU.ps1 new file mode 100644 index 0000000..cf428f7 --- /dev/null +++ b/Scripts/PSDCustomPreWU.ps1 @@ -0,0 +1,24 @@ +# // *************************************************************************** +# // +# // PowerShell Deployment for MDT +# // +# // File: PSDTemplate.ps1 +# // +# // Purpose: Apply the specified operating system. +# // +# // +# // *************************************************************************** + +param ( + +) + +# Load core modules +Import-Module Microsoft.BDD.TaskSequenceModule -Scope Global +Import-Module PSDUtility + +$verbosePreference = "Continue" + +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Load core modules" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Deployroot is now $($tsenv:DeployRoot)" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): env:PSModulePath is now $env:PSModulePath" diff --git a/Scripts/PSDDeploymentShare.psm1 b/Scripts/PSDDeploymentShare.psm1 index 201a83c..921b03a 100644 --- a/Scripts/PSDDeploymentShare.psm1 +++ b/Scripts/PSDDeploymentShare.psm1 @@ -9,9 +9,11 @@ # // # // *************************************************************************** -$verbosePreference = "Continue" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Importing module Bitstransfer" +#$VerbosePreference = "SilentlyContinue" Import-Module BitsTransfer -Global +#$verbosePreference = "Continue" # Local variables $global:psddsDeployRoot = "" @@ -33,6 +35,10 @@ function Get-PSDConnection $global:psddsDeployUser = $username $global:psddsDeployPassword = $password + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): global:psddsDeployRoot is now $global:psddsDeployRoot" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): global:psddsDeployUser is now $global:psddsDeployUser" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): global:psddsDeployPassword is now $global:psddsDeployPassword" + # Get credentials if (!$global:psddsDeployUser -or !$global:psddsDeployPassword) { @@ -59,7 +65,14 @@ function Get-PSDConnection } elseif ($global:psddsDeployRoot -like "\\*") { # Connect to a UNC path - New-PSDrive -Name (Get-PSDAvailableDriveLetter) -PSProvider FileSystem -Root $global:psddsDeployRoot -Credential $loalCredential -Scope Global + try + { + New-PSDrive -Name (Get-PSDAvailableDriveLetter) -PSProvider FileSystem -Root $global:psddsDeployRoot -Credential $global:psddsCredential -Scope Global + } + catch + { + + } Get-PSDProvider -DeployRoot $global:psddsDeployRoot } else @@ -78,20 +91,25 @@ function Get-PSDProvider [string] $deployRoot ) + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): deployRoot is now $deployRoot" + # Set an install directory if necessary (needed so the provider can find templates) if ((Test-Path "HKLM:\Software\Microsoft\Deployment 4") -eq $false) { $null = New-Item "HKLM:\Software\Microsoft\Deployment 4" Set-ItemProperty "HKLM:\Software\Microsoft\Deployment 4" -Name "Install_Dir" -Value "$deployRoot\" - Write-Verbose "Set MDT Install_Dir to $deployRoot\ for MDT Provider." + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Set MDT Install_Dir to $deployRoot\ for MDT Provider." } # Load the PSSnapIn PowerShell provider module $modules = Get-PSDContent -Content "Tools\Modules" + $VerbosePreference = "SilentlyContinue" Import-Module "$modules\Microsoft.BDD.PSSnapIn" + $verbosePreference = "Continue" + # Create the PSDrive - Write-Verbose "Creating MDT provider drive DeploymentShare: at $deployRoot" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Creating MDT provider drive DeploymentShare: at $deployRoot" New-PSDrive -Name DeploymentShare -PSProvider MDTProvider -Root $deployRoot -Scope Global } @@ -107,7 +125,6 @@ function Get-PSDAvailableDriveLetter } } - # Function for finding and retrieving the specified content. The source location specifies # a relative path within the deployment share. The destination specifies the local path where # the content should be placed. If no destination is specified, it will be placed in a @@ -119,43 +136,63 @@ function Get-PSDContent [string] $destination = "" ) + $verbosePreference = "Continue" + $dest = "" + # Track the time + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Track the time" $start = Get-Date # If the destination is blank, use a default value + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): If the destination is blank, use a default value" if ($destination -eq "") { - $dest = "$(Get-PSDLocalDataPath)\Cache\$content" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Destination is blank, setting value Dest" + $PSDLocalDataPath = Get-PSDLocalDataPath + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): PSDLocalDataPath is $PSDLocalDataPath" + $dest = "$PSDLocalDataPath\Cache\$content" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Dest is $dest" } else { + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Destination is NOT blank" $dest = $destination + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Dest is $dest" } # If the destination already exists, assume the content was already downloaded. + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): If the destination already exists, assume the content was already downloaded." # Otherwise, download it, copy it, . + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Otherwise, download it, copy it." + if (Test-Path $dest) { - Write-Verbose "Already copied $content, not copying again." + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Access to $dest is OK" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Already copied $content, not copying again." } elseif ($global:psddsDeployRoot -ilike "http*") { + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): global:psddsDeployRoot is now $global:psddsDeployRoot" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Running Get-PSDContentWeb -content $content -destination $dest" Get-PSDContentWeb -content $content -destination $dest } elseif ($global:psddsDeployRoot -like "\\*") { + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): global:psddsDeployRoot is now $global:psddsDeployRoot" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Running Get-PSDContentUNC -content $content -destination $dest" Get-PSDContentUNC -content $content -destination $dest } else { - Write-Verbose "Path for $content is already local, not copying" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Path for $content is already local, not copying again" } # Report the time + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Report the time" $elapsed = (Get-Date) - $start - Write-Verbose "Elapsed time to transfer $content : $elapsed" - - # Return the destinationf + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Elapsed time to transfer $content : $elapsed" + # Return the destination + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Return the destination $dest" return $dest } @@ -167,7 +204,7 @@ function Get-PSDContentUNC [string] $destination ) - Write-Verbose "Copying from $($global:psddsDeployRoot)\$content to $destination" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Copying from $($global:psddsDeployRoot)\$content to $destination" Copy-PSDFolder "$($global:psddsDeployRoot)\$content" $destination } @@ -178,7 +215,7 @@ function Get-PSDContentWeb [string] $content, [string] $destination ) - + $fullSource = "$($global:psddsDeployRoot)/$content" $fullSource = $fullSource.Replace("\", "/") $request = [System.Net.WebRequest]::Create($fullSource) @@ -191,16 +228,21 @@ function Get-PSDContentWeb $request.Headers.Set("Depth", "infinity") $request.Credentials = $global:psddsCredential - Write-Verbose "Retrieving directory listing of $fullSource via WebDAV." + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Retrieving directory listing of $fullSource via WebDAV." try { $response = $request.GetResponse() } catch { - Write-Verbose "Unable to retrieve directory listing." - Write-Verbose $_.Exception.InnerException - Write-Verbose $response + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Unable to retrieve directory listing!" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): $($_.Exception.InnerException)" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): $response" + $Message = "Unable to Retrieve directory listing of $($fullSource) via WebDAV. Error message: $($_.Exception.Message)" + Show-PSDInfo -Message "$($Message)" -Severity Error + Start-Process PowerShell -Wait + Break + } if ($response -ne $null) @@ -221,7 +263,7 @@ function Get-PSDContentWeb } $results += $obj } - Write-Verbose "Directory listing retrieved with $($results.Count) items." + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Directory listing retrieved with $($results.Count) items." # Create the folder structure $results | ? { $_.iscollection -eq "1"} | sort destination | % { @@ -236,25 +278,64 @@ function Get-PSDContentWeb } } - # If possible, do the transfer using BITS. Otherwise, download the files one at a time - if ($env:SYSTEMDRIVE -eq "X:") { + # If possible, do the transfer using ACP or BITS. Otherwise, download the files one at a time + if($tsenv:SMSTSDownloadProgram) + { + #We are using an ACP/ assume it works in WinPE as well. We use ACP as BITS does not function as regular BITS in WinPE, so cannot use PS cmdlet. + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Downloading files using ACP." + # Begin create regular ACP style .ini file + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Create regular ACP style .ini file" + + #Needed, do not remove. + $PSDPkgId = "PSD12345" + + # Create regular ACP style .ini file + $iniPath = "$env:tmp\$PSDPkgId"+"_Download.ini" + Set-Content -Value '[Download]' -Path $iniPath -Force -Encoding Ascii + Add-Content -Value "Source=$topUri" -Path $iniPath + Add-Content -Value "Destination=$destination" -Path $iniPath + Add-Content -Value "MDT=true" -Path $iniPath + + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Destination=$destination" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Source=$topUri" + + # ToDo, check that the ini file exists before we try... + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Downloading information saved to $iniPath so starting $tsenv:SMSTSDownloadProgram" + + if((Test-Path -Path $iniPath) -eq $true) + { + #Start-Process -Wait -FilePath "$tsenv:SMSTSDownloadProgram" -ArgumentList "$iniPath $PSDPkgId `"$($destination)`"" + Start-Process -Wait -WindowStyle Hidden -FilePath "$tsenv:SMSTSDownloadProgram" -ArgumentList "$iniPath $PSDPkgId `"$($destination)`"" + # ToDo hash verification? + } + else + { + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Unable to access $iniPath, aborting..." + Show-PSDInfo -Message "Unable to access $iniPath, aborting..." -Severity Information + Start-Process PowerShell -Wait + Exit 1 + } + } + + # If possible, do the transfer using BITS. Otherwise, download the files one at a time + elseif ($env:SYSTEMDRIVE -eq "X:") + { # In Windows PE, download the files one at a time using WebClient - Write-Verbose "Downloading files using WebClient." + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Downloading files using WebClient." $wc = New-Object System.Net.WebClient $wc.Credentials = $global:psddsCredential $results | ? { $_.iscollection -eq "0"} | sort destination | % { $href = $_.href $fullFile = "$destination\$($_.destination)" - # Write-Verbose "Downloading from $href to $fullFile" try { $wc.DownloadFile($href, $fullFile) } catch { - Write-Verbose "Unable to download file $href." - Write-Verbose $_.Exception.InnerException + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Unable to download file $href." + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): $($_.Exception.InnerException)" } } } @@ -267,15 +348,12 @@ function Get-PSDContentWeb $sourceUrl += [string]$_.href $fullFile = "$destination\$($_.destination)" $destFile += [string]$fullFile - # Write-Verbose "Adding $($_.href) to $fullFile" } - # Do the download using BITS - Write-Verbose "Downloading files using BITS." + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Downloading files using BITS." $bitsJob = Start-BitsTransfer -Authentication Ntlm -Credential $global:psddsCredential -Source $sourceUrl -Destination $destFile -TransferType Download -DisplayName "PSD Transfer" -Priority High } } - } Export-ModuleMember -function Get-PSDConnection @@ -286,7 +364,7 @@ if (Test-Path "tsenv:") { if ($tsenv:DeployRoot -ne "") { - Write-Verbose "Reconnecting to the deployment share at $($tsenv:DeployRoot)." + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Reconnecting to the deployment share at $($tsenv:DeployRoot)." if ($tsenv:UserDomain -ne "") { Get-PSDConnection -deployRoot $tsenv:DeployRoot -username "$($tsenv:UserDomain)\$($tsenv:UserID)" -password $tsenv:UserPassword diff --git a/Scripts/PSDDisableBDEProtectors.PS1 b/Scripts/PSDDisableBDEProtectors.PS1 new file mode 100644 index 0000000..8bae13c --- /dev/null +++ b/Scripts/PSDDisableBDEProtectors.PS1 @@ -0,0 +1,22 @@ +# // *************************************************************************** +# // +# // PowerShell Deployment for MDT +# // +# // File: PSDTemplate.ps1 +# // +# // Purpose: Apply the specified operating system. +# // +# // +# // *************************************************************************** + +param ( + +) + +# Load core modules +Import-Module PSDUtility +Import-Module PSDDeploymentShare +$verbosePreference = "Continue" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Load core modules" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Deployroot is now $deployRoot" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): env:PSModulePath is now $env:PSModulePath" diff --git a/Scripts/PSDDrivers.ps1 b/Scripts/PSDDrivers.ps1 new file mode 100644 index 0000000..c8e0b00 --- /dev/null +++ b/Scripts/PSDDrivers.ps1 @@ -0,0 +1,62 @@ +# // *************************************************************************** +# // +# // PowerShell Deployment for MDT +# // +# // File: PSDDrivers.ps1 +# // +# // Purpose: Apply the specified operating system. +# // +# // +# // *************************************************************************** + +param ( + +) + + + +# Load core modules +Import-Module Microsoft.BDD.TaskSequenceModule -Scope Global +Import-Module DISM +Import-Module PSDUtility +Import-Module PSDDeploymentShare + +$verbosePreference = "Continue" + +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Load core modules" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Deployroot is now $($tsenv:DeployRoot)" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): env:PSModulePath is now $env:PSModulePath" + +# Building source and destionation paths based on model DriverGroup001 +$BaseDriverPath = "DriverPackages" +$SourceDriverPackagePath = ($BaseDriverPath + "\" + ($tsenv:DriverGroup001).Replace("\"," - ")).replace(" ","_") +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): tsenv:DriverGroup001 is $($tsenv:DriverGroup001)" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): SourceDriverPackagePath is now $SourceDriverPackagePath" + +#Copy drivers to cache +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Copy $SourceDriverPackagePath to cache " +Get-PSDContent -content $SourceDriverPackagePath + +#Get all ZIP files from the cache +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Getting drivers..." +$Zips = Get-ChildItem -Path "$($tsenv:OSVolume):\MININT\Cache\DriverPackages" -Filter *.zip -Recurse + +#Did we find any? +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Found $($Zips.count) packages" +Foreach($Zip in $Zips) +{ + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Unpacking $($Zip.FullName)" + #Need to use this method, since the assemblys can not be loaded due to a issue... + Start PowerShell -ArgumentList "Expand-Archive -Path $($Zip.FullName) -DestinationPath $($tsenv:OSVolume):\Drivers -Force -Verbose" -Wait +} + +Start-Sleep -Seconds 1 + +#What do we have here +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Get list of drivers from \Drivers" +$Drivers = Get-ChildItem -Path "$($tsenv:OSVolume):\Drivers" -Filter *.inf -Recurse +foreach($Driver in $Drivers){ + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): $($Driver.Name) is now in the \Drivers folder" + $TSxDriverInfo = Get-PSDDriverInfo -Path $Driver.FullName + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Driverinfo: Name:$($TSxDriverInfo.Name) Vendor:$($TSxDriverInfo.Manufacturer) Class:$($TSxDriverInfo.Class) Date:$($TSxDriverInfo.Date) Version:$($TSxDriverInfo.Version)" +} diff --git a/Scripts/PSDErrorInTS.ps1 b/Scripts/PSDErrorInTS.ps1 new file mode 100644 index 0000000..cf428f7 --- /dev/null +++ b/Scripts/PSDErrorInTS.ps1 @@ -0,0 +1,24 @@ +# // *************************************************************************** +# // +# // PowerShell Deployment for MDT +# // +# // File: PSDTemplate.ps1 +# // +# // Purpose: Apply the specified operating system. +# // +# // +# // *************************************************************************** + +param ( + +) + +# Load core modules +Import-Module Microsoft.BDD.TaskSequenceModule -Scope Global +Import-Module PSDUtility + +$verbosePreference = "Continue" + +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Load core modules" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Deployroot is now $($tsenv:DeployRoot)" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): env:PSModulePath is now $env:PSModulePath" diff --git a/Scripts/PSDFreshen.ps1 b/Scripts/PSDFreshen.ps1 index e13a4a6..80b50a6 100644 --- a/Scripts/PSDFreshen.ps1 +++ b/Scripts/PSDFreshen.ps1 @@ -6,12 +6,31 @@ # // # // Purpose: Update gathered information in the task sequence environment. # // +# // # // *************************************************************************** +param ( + +) + # Load core module +Import-Module Microsoft.BDD.TaskSequenceModule -Scope Global Import-Module PSDUtility Import-Module PSDGather + $verbosePreference = "Continue" +#Write-Verbose -Message "$($MyInvocation.MyCommand.Name): Load core module" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Load core modules" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Deployroot is now $($tsenv:DeployRoot)" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): env:PSModulePath is now $env:PSModulePath" + # Gather local info to make sure key variables are set (e.g. Architecture) +#Write-Verbose -Message "$($MyInvocation.MyCommand.Name): Gather local info to make sure key variables are set (e.g. Architecture)" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Gather local info to make sure key variables are set (e.g. Architecture)" Get-PSDLocalInfo + +# Save all the current variables for later use +#Write-Verbose -Message "$($MyInvocation.MyCommand.Name): Save all the current variables for later use" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Save all the current variables for later use" +Save-PSDVariables diff --git a/Scripts/PSDGather.ps1 b/Scripts/PSDGather.ps1 new file mode 100644 index 0000000..27458a6 --- /dev/null +++ b/Scripts/PSDGather.ps1 @@ -0,0 +1,38 @@ +# // *************************************************************************** +# // +# // PowerShell Deployment for MDT +# // +# // File: PSDGather.ps1 +# // +# // Purpose: Update gathered information in the task sequence environment. +# // +# // +# // *************************************************************************** + +param ( + +) + +# Load core module +Import-Module Microsoft.BDD.TaskSequenceModule -Scope Global +Import-Module PSDUtility +Import-Module PSDGather + +$verbosePreference = "Continue" + +#Write-Verbose -Message "$($MyInvocation.MyCommand.Name): Load core module" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Load core modules" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Deployroot is now $($tsenv:DeployRoot)" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): env:PSModulePath is now $env:PSModulePath" + +Write-PSDEvent -MessageID 41000 -severity 1 -Message "Starting: $($MyInvocation.MyCommand.Name)" + +# Gather local info to make sure key variables are set (e.g. Architecture) +#Write-Verbose -Message "$($MyInvocation.MyCommand.Name): Gather local info to make sure key variables are set (e.g. Architecture)" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Gather local info to make sure key variables are set (e.g. Architecture)" +Get-PSDLocalInfo + +# Save all the current variables for later use +#Write-Verbose -Message "$($MyInvocation.MyCommand.Name): Save all the current variables for later use" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Save all the current variables for later use" +Save-PSDVariables diff --git a/Scripts/PSDGather.psm1 b/Scripts/PSDGather.psm1 index 0061672..0ddcc05 100644 --- a/Scripts/PSDGather.psm1 +++ b/Scripts/PSDGather.psm1 @@ -11,7 +11,10 @@ # // # // *************************************************************************** -Function Get-PSDLocalInfo { +#$verbosePreference = "Continue" + +Function Get-PSDLocalInfo +{ Process { # Look up OS details @@ -56,12 +59,17 @@ Function Get-PSDLocalInfo { $tsenv:IsDesktop = "False" $tsenv:IsLaptop = "False" $tsenv:IsServer = "False" + $tsenv:IsSFF = "False" + $tsenv:IsTablet = "False" Get-WmiObject Win32_SystemEnclosure | % { $tsenv:AssetTag = $_.SMBIOSAssetTag.Trim() if ($_.ChassisTypes[0] -in "8", "9", "10", "11", "12", "14", "18", "21") { $tsenv:IsLaptop = "True" } if ($_.ChassisTypes[0] -in "3", "4", "5", "6", "7", "15", "16") { $tsenv:IsDesktop = "True" } if ($_.ChassisTypes[0] -in "23") { $tsenv:IsServer = "True" } + if ($_.ChassisTypes[0] -in "34", "35", "36") { $tsenv:IsSFF = "True" } + if ($_.ChassisTypes[0] -in "13", "31", "32", "30") { $tsenv:IsTablet = "True" } } + Get-WmiObject Win32_BIOS | % { $tsenv:SerialNumber = $_.SerialNumber.Trim() } @@ -89,11 +97,12 @@ Function Get-PSDLocalInfo { } # TODO: Capable architecture + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): TODO: Capable architecture" Get-WmiObject Win32_ComputerSystem | % { $tsenv:Manufacturer = $_.Manufacturer $tsenv:Model = $_.Model - $tsenv:Memory = [int] ($m.TotalPhysicalMemory / 1024 / 1024) + $tsenv:Memory = [int] ($_.TotalPhysicalMemory / 1024 / 1024) } Get-WmiObject Win32_ComputerSystemProduct | % { @@ -115,19 +124,84 @@ Function Get-PSDLocalInfo { $tsenv:IsUEFI = "False" } - # TODO: Battery + # TEST: Battery + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): TEST: Battery" + + $bFoundAC = $false + $bOnBattery = $false + $bFoundBattery = $false + foreach($Battery in (Get-WmiObject -Class Win32_Battery)) + { + $bFoundBattery = $true + if ($Battery.BatteryStatus -eq "2") + { + $bFoundAC = $true + } + } + If ($bFoundBattery -and !$bFoundAC) + { + $tsenv.IsOnBattery = $true + } + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): bFoundAC: $bFoundAC" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): bOnBattery :$bOnBattery" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): bFoundBattery: $bFoundBattery" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): tsenv.IsOnBattery is now $($tsenv.IsOnBattery)" # TODO: GetDP + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): TODO: GetDP" + # TODO: GetWDS + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): TODO: GetWDS" + # TODO: GetHostName + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): TODO: GetHostName" + # TODO: GetOSSKU + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): TODO: GetOSSKU" + # TODO: GetCurrentOSInfo + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): TODO: GetCurrentOSInfo" + # TODO: Virtualization + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): TEST: Virtualization" + + $Win32_ComputerSystem = Get-WmiObject -Class Win32_ComputerSystem + switch ($Win32_ComputerSystem.model) + { + "Virtual Machine" + { + $tsenv:IsVM = "True" + } + "VMware Virtual Platform" + { + $tsenv:IsVM = "True" + } + "VMware7,1" + { + $tsenv:IsVM = "True" + } + "Virtual Box" + { + $tsenv:IsVM = "True" + } + Default + { + $tsenv:IsVM = "False" + } + } + + + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Model is $($Win32_ComputerSystem.model)" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): tsenv:IsVM is now $tsenv:IsVM" + # TODO: BitLocker + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): TODO: BitLocker" + } } -Function Invoke-PSDRules { +Function Invoke-PSDRules +{ [CmdletBinding()] Param( [ValidateNotNullOrEmpty()] @@ -159,7 +233,7 @@ Function Invoke-PSDRules { } $newVar.overwrite = "false" $newVar.description = "Custom property" - Write-Verbose "Adding custom property $($newVar.id)" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Adding custom property $($newVar.id)" $null = $global:variableFile.properties.appendChild($newVar) } } @@ -185,27 +259,28 @@ Function Invoke-PSDRule } Process { - Write-Verbose "Processing rule $RuleName" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Processing rule $RuleName" + $v = $global:variables | ? {$_.id -ieq $RuleName} if ($RuleName.ToUpper() -eq "DEFAULTGATEWAY") { - Write-Host "TODO: Process default gateway" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): TODO: Process default gateway" } elseif ($v) { if ($v.type -eq "list") { - Write-Verbose "Processing values of $RuleName" - (get-item tsenvlist:$($v.id)).Value | Invoke-PSDRule + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Processing values of $RuleName" + (Get-Item tsenvlist:$($v.id)).Value | Invoke-PSDRule } else { $s = (Get-Item tsenv:$($v.id)).Value if ($s -ne "") { - Write-Verbose "Processing value of $RuleName" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Processing value of $RuleName" Invoke-PSDRule $s } else { - Write-Verbose "Skipping rule $RuleName, value is blank" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Skipping rule $RuleName, value is blank" } } } @@ -237,22 +312,23 @@ Function Get-PSDSettings } # Process special sections and exits - if ($section.Contains("UserExit")) { - Write-Host "TODO: Process UserExit Before" + if ($section.Contains("UserExit")) + { + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): TODO: Process UserExit Before" } if ($section.Contains("SQLServer")) { $skipProperties = $true - Write-Host "TODO: Database" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): TODO: Database" } if ($section.Contains("WebService")) { $skipProperties = $true - Write-Host "TODO: WebService" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): TODO: WebService" } if ($section.Contains("Subsection")) { - Write-Verbose "Processing subsection" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Processing subsection" Invoke-PSDRule $section["Subsection"] } @@ -263,16 +339,18 @@ Function Get-PSDSettings $v = $global:variables | ? {$_.id -ieq $sectionVar} if ($v) { - if ((get-item tsenv:$v).Value -eq $section[$sectionVar]) + if ((Get-Item tsenv:$v).Value -eq $section[$sectionVar]) { # Do nothing, value unchanged } - if ((get-item tsenv:$v).Value -eq "" -or $v.overwrite -eq "true") { - Write-Verbose "Changing $($v.id) to $($section[$sectionVar]), was $((Get-Item tsenv:$($v.id)).Value)" + if ((Get-Item tsenv:$v).Value -eq "" -or $v.overwrite -eq "true") { + $Value = $((Get-Item tsenv:$($v.id)).Value) + if($value -eq ''){$value = "EMPTY"} + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Changing PROPERTY $($v.id) to $($section[$sectionVar]), was $Value" Set-Item tsenv:$($v.id) -Value $section[$sectionVar] } - elseif ((get-item tsenv:$v).Value -ne "") { - Write-Verbose "Ignoring new value for $($v.id)" + elseif ((Get-Item tsenv:$v).Value -ne "") { + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Ignoring new value for $($v.id)" } } else @@ -282,7 +360,7 @@ Function Get-PSDSettings if ($v) { if ($v.type -eq "list") { - Write-Verbose "Adding $($section[$sectionVar]) to $($v.id)" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Adding $($section[$sectionVar]) to $($v.id)" $n = @((Get-Item tsenvlist:$($v.id)).Value) $n += [String] $section[$sectionVar] Set-Item tsenvlist:$($v.id) -Value $n @@ -293,13 +371,14 @@ Function Get-PSDSettings } if ($section.Contains("UserExit")) { - Write-Host "TODO: Process UserExit After" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): TODO: Process UserExit After" } } } -Function Get-IniContent { +Function Get-IniContent +{ <# .Synopsis Gets the content of an INI file @@ -351,7 +430,8 @@ Function Get-IniContent { #> [CmdletBinding()] - Param( + Param + ( [ValidateNotNullOrEmpty()] [ValidateScript({(Test-Path $_) -and ((Get-Item $_).Extension -eq ".ini")})] [Parameter(ValueFromPipeline=$True,Mandatory=$True)] @@ -359,11 +439,13 @@ Function Get-IniContent { ) Begin - {Write-Verbose "$($MyInvocation.MyCommand.Name):: Function started"} + { + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Function started" + } Process { - Write-Verbose "$($MyInvocation.MyCommand.Name):: Processing file: $Filepath" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Processing file: $Filepath" $ini = @{} switch -regex -file $FilePath @@ -397,10 +479,12 @@ Function Get-IniContent { $ini[$section][$name] = $value } } - Write-Verbose "$($MyInvocation.MyCommand.Name):: Finished Processing file: $FilePath" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Finished Processing file: $FilePath" Return $ini } End - {Write-Verbose "$($MyInvocation.MyCommand.Name):: Function ended"} + { + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Function ended" + } } diff --git a/Scripts/PSDGroups.ps1 b/Scripts/PSDGroups.ps1 new file mode 100644 index 0000000..8bae13c --- /dev/null +++ b/Scripts/PSDGroups.ps1 @@ -0,0 +1,22 @@ +# // *************************************************************************** +# // +# // PowerShell Deployment for MDT +# // +# // File: PSDTemplate.ps1 +# // +# // Purpose: Apply the specified operating system. +# // +# // +# // *************************************************************************** + +param ( + +) + +# Load core modules +Import-Module PSDUtility +Import-Module PSDDeploymentShare +$verbosePreference = "Continue" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Load core modules" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Deployroot is now $deployRoot" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): env:PSModulePath is now $env:PSModulePath" diff --git a/Scripts/PSDHelper.ps1 b/Scripts/PSDHelper.ps1 new file mode 100644 index 0000000..bd002b8 --- /dev/null +++ b/Scripts/PSDHelper.ps1 @@ -0,0 +1,24 @@ +#PSD Helper +Param( + $MDTDeploySharePath, + $UserName, + $Password +) + +#Connect +& net use $MDTDeploySharePath $Password /USER:$UserName + +# Set the module path based on the current script path +$deployRoot = Split-Path -Path "$PSScriptRoot" +$env:PSModulePath = $env:PSModulePath + ";$deployRoot\Tools\Modules" + + +#Import Env +Import-Module Microsoft.BDD.TaskSequenceModule -Scope Global -Force -Verbose +Import-Module PSDUtility -Force -Verbose +Import-Module PSDDeploymentShare -Force -Verbose +Import-Module PSDGather -Force -Verbose + +dir tsenv: | Out-File "$($env:SystemDrive)\DumpVars.log" +Get-Content -Path "$($env:SystemDrive)\DumpVars.log" + diff --git a/Scripts/PSDNextPhase.ps1 b/Scripts/PSDNextPhase.ps1 new file mode 100644 index 0000000..b02d174 --- /dev/null +++ b/Scripts/PSDNextPhase.ps1 @@ -0,0 +1,47 @@ +# // *************************************************************************** +# // +# // PowerShell Deployment for MDT +# // +# // File: PSDNextPhase.ps1 +# // +# // Purpose: Next PHASE +# // +# // +# // *************************************************************************** + +param ( + +) + +# Load core modules +Import-Module Microsoft.BDD.TaskSequenceModule -Scope Global +Import-Module PSDUtility +Import-Module PSDDeploymentShare + +$verbosePreference = "Continue" + +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Load core modules" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Deployroot is now $($tsenv:DeployRoot)" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): env:PSModulePath is now $env:PSModulePath" + +#Next Phase +$PHASE = $tsenv:PHASE +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Current Phase is $PHASE" + +switch ($PHASE) +{ + INITIALIZATION {$PHASE = "VALIDATION"} + VALIDATION {$PHASE = "STATECAPTURE"} + STATECAPTURE {$PHASE = "PREINSTALL"} + PREINSTALL {$PHASE = "INSTALL"} + INSTALL {$PHASE = "POSTINSTALL"} + POSTINSTALL {$PHASE = "STATERESTORE"} + STATERESTORE {$PHASE = ""} +} + +$tsenv:PHASE = $Phase + +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): --------------------" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Next Phase is $PHASE" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Save all the current variables for later use" +Save-PSDVariables diff --git a/Scripts/PSDNicConfig.ps1 b/Scripts/PSDNicConfig.ps1 new file mode 100644 index 0000000..8bae13c --- /dev/null +++ b/Scripts/PSDNicConfig.ps1 @@ -0,0 +1,22 @@ +# // *************************************************************************** +# // +# // PowerShell Deployment for MDT +# // +# // File: PSDTemplate.ps1 +# // +# // Purpose: Apply the specified operating system. +# // +# // +# // *************************************************************************** + +param ( + +) + +# Load core modules +Import-Module PSDUtility +Import-Module PSDDeploymentShare +$verbosePreference = "Continue" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Load core modules" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Deployroot is now $deployRoot" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): env:PSModulePath is now $env:PSModulePath" diff --git a/Scripts/PSDPartition.ps1 b/Scripts/PSDPartition.ps1 index 1522b8e..c553839 100644 --- a/Scripts/PSDPartition.ps1 +++ b/Scripts/PSDPartition.ps1 @@ -6,13 +6,27 @@ # // # // Purpose: Partition the disk # // +# // # // *************************************************************************** +param ( + +) + # Load core modules +Import-Module Microsoft.BDD.TaskSequenceModule -Scope Global Import-Module PSDUtility + $verbosePreference = "Continue" +#Write-Verbose -Message "$($MyInvocation.MyCommand.Name): Load core modules" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Load core modules" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Deployroot is now $($tsenv:DeployRoot)" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): env:PSModulePath is now $env:PSModulePath" + # Keep the logging out of the way +#Write-Verbose -Message "$($MyInvocation.MyCommand.Name): Keep the logging out of the way" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Keep the logging out of the way" $currentLocalDataPath = Get-PSDLocalDataPath if ($currentLocalDataPath -NotLike "X:\*") { @@ -25,35 +39,49 @@ if ($currentLocalDataPath -NotLike "X:\*") } # Partition and format the disk +#Write-Verbose -Message "$($MyInvocation.MyCommand.Name): Partition and format the disk" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Partition and format the disk" Update-Disk -Number 0 $disk = Get-Disk -Number 0 if ($tsenv:IsUEFI -eq "True") { # UEFI partitioning + #Write-Verbose -Message "$($MyInvocation.MyCommand.Name): UEFI partitioning" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): UEFI partitioning" # Clean the disk if it isn't raw + #Write-Verbose -Message "$($MyInvocation.MyCommand.Name): Clean the disk if it isn't raw" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Clean the disk if it isn't raw" if ($disk.PartitionStyle -ne "RAW") { - Write-Verbose "Clearing disk" + #Write-Verbose -Message "$($MyInvocation.MyCommand.Name): Clearing disk" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Clearing disk" Clear-Disk -Number 0 -RemoveData -RemoveOEM -Confirm:$false } # Initialize the disk - Write-Verbose "Initializing disk" + #Write-Verbose -Message "$($MyInvocation.MyCommand.Name): Initialize the disk" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Initialize the disk" Initialize-Disk -Number 0 -PartitionStyle GPT Get-Disk -Number 0 # Calculate the OS partition size, as we want a recovery partiton after it - $osSize = $disk.Size - 499MB - 128MB - 499MB + #Write-Verbose -Message "$($MyInvocation.MyCommand.Name): Calculate the OS partition size, as we want a recovery partiton after it" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Calculate the OS partition size, as we want a recovery partiton after it" + $osSize = $disk.Size - 499MB - 128MB - 1024MB # Create the paritions + #Write-Verbose -Message "$($MyInvocation.MyCommand.Name): Create the paritions" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Create the paritions" $efi = New-Partition -DiskNumber 0 -Size 499MB -AssignDriveLetter $msr = New-Partition -DiskNumber 0 -Size 128MB -GptType '{e3c9e316-0b5c-4db8-817d-f92df00215ae}' $os = New-Partition -DiskNumber 0 -Size $osSize -AssignDriveLetter $recovery = New-Partition -DiskNumber 0 -UseMaximumSize -AssignDriveLetter -GptType '{de94bba4-06d1-4d40-a16a-bfd50179d6ac}' # Save the drive letters and volume GUIDs to task sequence variables + #Write-Verbose -Message "$($MyInvocation.MyCommand.Name): Save the drive letters and volume GUIDs to task sequence variables" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Save the drive letters and volume GUIDs to task sequence variables" $tsenv:BootVolume = $efi.DriveLetter $tsenv:BootVolumeGuid = $efi.Guid $tsenv:OSVolume = $os.DriveLetter @@ -62,63 +90,125 @@ if ($tsenv:IsUEFI -eq "True") $tsenv:RecoveryVolumeGuid = $recovery.Guid # Format the volumes + #Write-Verbose -Message "$($MyInvocation.MyCommand.Name): Format the volumes" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Format the volumes" Format-Volume -DriveLetter $tsenv:BootVolume -FileSystem FAT32 Format-Volume -DriveLetter $tsenv:OSVolume -FileSystem NTFS Format-Volume -DriveLetter $tsenv:RecoveryVolume -FileSystem NTFS + + } else { # Clean the disk if it isn't raw + #Write-Verbose -Message "$($MyInvocation.MyCommand.Name): Clean the disk if it isn't raw" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Clean the disk if it isn't raw" if ($disk.PartitionStyle -ne "RAW") { - Write-Verbose "Clearing disk" + #Write-Verbose -Message "$($MyInvocation.MyCommand.Name): Clearing disk" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Clearing disk" Clear-Disk -Number 0 -RemoveData -RemoveOEM -Confirm:$false } # Initialize the disk - Write-Verbose "Initializing disk" + #Write-Verbose -Message "$($MyInvocation.MyCommand.Name): Initialize the disk" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Initialize the disk" Initialize-Disk -Number 0 -PartitionStyle MBR Get-Disk -Number 0 # Calculate the OS partition size, as we want a recovery partiton after it - $osSize = $disk.Size - 499MB - 499MB + #Write-Verbose -Message "$($MyInvocation.MyCommand.Name): Calculate the OS partition size, as we want a recovery partiton after it" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Calculate the OS partition size, as we want a recovery partiton after it" + $osSize = $disk.Size - 499MB - 1024MB # Create the paritions + #Write-Verbose -Message "$($MyInvocation.MyCommand.Name): Create the partitions" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Create the partitions" $boot = New-Partition -DiskNumber 0 -Size 499MB -AssignDriveLetter -IsActive $os = New-Partition -DiskNumber 0 -Size $osSize -AssignDriveLetter $recovery = New-Partition -DiskNumber 0 -UseMaximumSize -AssignDriveLetter # Save the drive letters and volume GUIDs to task sequence variables + #Write-Verbose -Message "$($MyInvocation.MyCommand.Name): Save the drive letters and volume GUIDs to task sequence variables" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Save the drive letters and volume GUIDs to task sequence variables" + # Modified for better output (admminy) $tsenv:BootVolume = $boot.DriveLetter - $tsenv:BootVolumeGuid = $boot.Guid + #Write-Verbose -Message "$($MyInvocation.MyCommand.Name): tsenv:BootVolume is now $tsenv:BootVolume" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): tsenv:BootVolume is now $tsenv:BootVolume" + + # Modified for better output (admminy) $tsenv:OSVolume = $os.DriveLetter - $tsenv:OSVolumeGuid = $os.Guid + #Write-Verbose -Message "$($MyInvocation.MyCommand.Name): tsenv:OSVolume is now $tsenv:OSVolume" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): tsenv:OSVolume is now $tsenv:OSVolume" + + # Modified for better output (admminy) $tsenv:RecoveryVolume = $recovery.DriveLetter - $tsenv:RecoveryVolumeGuid = $recovery.Guid + #Write-Verbose -Message "$($MyInvocation.MyCommand.Name): tsenv:RecoveryVolume $tsenv:RecoveryVolume" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): tsenv:RecoveryVolume $tsenv:RecoveryVolume" + + # Removed, there is no guid on MDR disks (admminy) + # $tsenv:RecoveryVolumeGuid = $recovery.Guid + # $tsenv:BootVolumeGuid = $boot.Guid + # $tsenv:OSVolumeGuid = "MBR" + + # Format the partitions (admminy) + #Write-Verbose -Message "$($MyInvocation.MyCommand.Name): Format the partitions (admminy)" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Format the partitions (admminy)" + Format-Volume -DriveLetter $tsenv:BootVolume -FileSystem NTFS -Verbose + Format-Volume -DriveLetter $tsenv:OSVolume -FileSystem NTFS -Verbose + Format-Volume -DriveLetter $tsenv:RecoveryVolume -FileSystem NTFS -Verbose + + #Fix for MBR + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Getting Guids from the volumes" + + $tsenv:OSVolumeGuid = (Get-Volume | Where-Object Driveletter -EQ $tsenv:OSVolume).UniqueId.replace("\\?\Volume","").replace("\","") + $tsenv:RecoveryVolumeGuid = (Get-Volume | Where-Object Driveletter -EQ $tsenv:RecoveryVolume).UniqueId.replace("\\?\Volume","").replace("\","") + $tsenv:BootVolumeGuid = (Get-Volume | Where-Object Driveletter -EQ $tsenv:BootVolume).UniqueId.replace("\\?\Volume","").replace("\","") + + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): tsenv:OSVolumeGuid is now $tsenv:OSVolumeGuid" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): tsenv:RecoveryVolumeGuid is now $tsenv:RecoveryVolumeGuid" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): tsenv:BootVolumeGuid is now $tsenv:BootVolumeGuid" - # Format the partitions - Format-Volume -DriveLetter $tsenv:BootVolume -FileSystem NTFS - Format-Volume -DriveLetter $tsenv:OSVolume -FileSystem NTFS - Format-Volume -DriveLetter $tsenv:RecoveryVolume -FileSystem NTFS } -# Make sure there is a PSDrive for he OS volume +# Make sure there is a PSDrive for the OS volume +#Write-Verbose -Message "$($MyInvocation.MyCommand.Name): Make sure there is a PSDrive for the OS volume" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Make sure there is a PSDrive for the OS volume" if ((Test-Path "$($tsenv:OSVolume):\") -eq $false) { - New-PSDrive -Name $tsenv:OSVolume -PSProvider FileSystem -Root "$($tsenv:OSVolume):\" + + New-PSDrive -Name $tsenv:OSVolume -PSProvider FileSystem -Root "$($tsenv:OSVolume):\" -Verbose } # If the old local data path survived the partitioning, copy it to the new location +#Write-Verbose -Message "$($MyInvocation.MyCommand.Name): If the old local data path survived the partitioning, copy it to the new location" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): If the old local data path survived the partitioning, copy it to the new location" if (Test-Path $currentLocalDataPath) { # Copy files to new data path + #Write-Verbose -Message "$($MyInvocation.MyCommand.Name): Copy files to new data path" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Copy files to new data path" $newLocalDataPath = Get-PSDLocalDataPath -Move if ($currentLocalDataPath -ine $newLocalDataPath) { - Write-Verbose "Copying $currentLocalDataPath to $newLocalDataPath" + #Write-Verbose -Message "$($MyInvocation.MyCommand.Name): Copying $currentLocalDataPath to $newLocalDataPath" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Copying $currentLocalDataPath to $newLocalDataPath" Copy-PSDFolder $currentLocalDataPath $newLocalDataPath + + # Change log location for TSxLogPath, since we now have a volume + $Global:TSxLogPath = "$newLocalDataPath\SMSOSD\OSDLOGS\PSDPartition.log" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Now logging to $Global:TSxLogPath" } } +# Dumping out variables for troubleshooting +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Dumping out variables for troubleshooting" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): tsenv:BootVolume is $tsenv:BootVolume" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): tsenv:OSVolume is $tsenv:OSVolume" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): tsenv:RecoveryVolume is $tsenv:RecoveryVolume" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): tsenv:IsUEFI is $tsenv:IsUEFI" + # Save all the current variables for later use +#Write-Verbose -Message "$($MyInvocation.MyCommand.Name): Save all the current variables for later use" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Save all the current variables for later use" Save-PSDVariables diff --git a/Scripts/PSDSetVariable.ps1 b/Scripts/PSDSetVariable.ps1 new file mode 100644 index 0000000..fc8f457 --- /dev/null +++ b/Scripts/PSDSetVariable.ps1 @@ -0,0 +1,47 @@ +# // *************************************************************************** +# // +# // PowerShell Deployment for MDT +# // +# // File: PSDSetVariables.ps1 +# // +# // Purpose: Set variable +# // +# // +# // *************************************************************************** + +param ( + +) + +# Load core modules +Import-Module Microsoft.BDD.TaskSequenceModule -Scope Global +Import-Module PSDUtility + +$verbosePreference = "Continue" + +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Load core modules" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Deployroot is now $($tsenv:DeployRoot)" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): env:PSModulePath is now $env:PSModulePath" + +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): TSEnv:VariableName is now $TSEnv:VariableName" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): TSEnv:VariableValue is now $TSEnv:VariableValue" + +$VariableName = $TSEnv:VariableName +$VariableValue = $TSEnv:VariableValue +New-Item -Path TSEnv: -Name "$VariableName" -Value "$VariableValue" -Force + +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): $VariableName is now $((Get-ChildItem -Path TSEnv: | Where-Object Name -Like $VariableName).value)" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Save all the current variables for later use" +Save-PSDVariables + +BREAK +<# +DriverGroup001 +Client\Windows 10 1709\%model% +oEnvironment.Item(oEnvironment.Item("VariableName")) = oEnvironment.Item("VariableValue") +$tsenv:DeployRoot + +powershell.exe -executionpolicy bypass -command "& {$tsenv = New-Object -COMObject Microsoft.SMS.TSEnvironment; $tsenv.Value('ImageVersion') = get-date -uformat %m%d%Y}" + +#> + diff --git a/Scripts/PSDStart.ps1 b/Scripts/PSDStart.ps1 index ab98a95..d55f04c 100644 --- a/Scripts/PSDStart.ps1 +++ b/Scripts/PSDStart.ps1 @@ -6,27 +6,114 @@ # // # // Purpose: Start or continue a PSD task sequence. # // +# // +# // Version 9.1 - Added check for network access when doing network deployment +# // Version 9.2 - Check that needed files are in WinPE for XAML files to show correctly +# // Logic for detection if running in WinPE +# // Check for unsupported variables # // *************************************************************************** param ( - [switch] $start + [switch] $start, + [switch] $PSDDeBug ) +#Check for PSDDeBug +if($PSDDeBug -eq $true) +{ + $verbosePreference = "Continue" + Write-Verbose "verbosePreference is now $verbosePreference" +} + # Set the module path based on the current script path $deployRoot = Split-Path -Path "$PSScriptRoot" $env:PSModulePath = $env:PSModulePath + ";$deployRoot\Tools\Modules" +# Verify the module path based on the current script path +if($PSDDeBug -eq $true) +{ + $verbosePreference = "Continue" + Write-Verbose $deployRoot + Write-Verbose $PSScriptRoot + Write-Verbose $env:PSModulePath +} + +#Check if we booted from WinPE +$BootfromWinPE = $false +if ($env:SYSTEMDRIVE -eq "X:") +{ + $BootfromWinPE = $true +} +if($PSDDeBug -eq $true) +{ + Write-Verbose "BootfromWinPE is now $BootfromWinPE" +} + # Load core module -Import-Module PSDUtility -Import-Module PSDDeploymentShare -Import-Module PSDGather -Import-Module PSDWizard +Import-Module PSDUtility -Force + +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): --------------------" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Beginning initial process in PSDStart.ps1" + +# Set Command Window size +# Reason for 99 is that 99 seems to use the screen in the best possible way, 100 is just one pixel to much +Set-PSDCommandWindowsSize -Width 99 -Height 15 + +if($BootfromWinPE -eq $true) +{ + + # Windows ADK v1809 could be missing certain files, we need to check for that. + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Check if we are running Windows ADK 10 v1809" + if($(Get-WmiObject Win32_OperatingSystem).BuildNumber -eq "17763") + { + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Check for BCP47Langs.dll and BCP47mrm.dll, needed for WPF" + if(-not(Test-Path -Path X:\Windows\System32\BCP47Langs.dll) -or -not(Test-Path -Path X:\Windows\System32\BCP47mrm.dll)) + { + Start-Process PowerShell -ArgumentList { + "Write-warning -Message 'We are missing the BCP47Langs.dll and BCP47mrm.dll files required for WinPE 1809.';Write-warning -Message 'Please check the PSD documentation on how to add those files.';Write-warning -Message 'Critical error, deployment can not continue..';Pause" + } -Wait + exit 1 + } + } + + # We need more than 1.5 GB (Testing for at least 1499MB of RAM) + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Check for minimum amount of memory in WinPE to run PSD" + if ((Get-WmiObject -Class Win32_computersystem).TotalPhysicalMemory -le 1499MB){ + Show-PSDInfo -Message "Not enough memory to run PSD, aborting..." -Severity Error + Start-Process PowerShell -Wait + exit 1 + } + + # All tests succeded, log that info + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Completed WinPE prerequisite checks" +} + +Import-Module PSDDeploymentShare -Force -ErrorAction Stop +Import-Module PSDGather -Force -ErrorAction Stop +Import-Module PSDWizard -Force -ErrorAction Stop + $verbosePreference = "Continue" +#Check if tsenv: works +try +{ + Get-ChildItem -Path "TSEnv:" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Able to read from TSEnv" +} +catch +{ + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Unable to read from TSEnv" + #Break +} + +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Load core modules" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Deployroot is now $deployRoot" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): env:PSModulePath is now $env:PSModulePath" + # If running from RunOnce, create a startup folder item and then exit if ($start) { - Write-Verbose "Creating a link to re-run $PSCommandPath from the all users Startup folder" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Creating a link to re-run $PSCommandPath from the all users Startup folder" # Create a shortcut to run this script $allUsersStartup = [Environment]::GetFolderPath('CommonStartup') @@ -36,28 +123,49 @@ if ($start) $shortcut.TargetPath = "powershell.exe" $shortcut.Arguments = "-noprofile -executionpolicy bypass -file $PSCommandPath" $shortcut.Save() - exit 0 } # Gather local info to make sure key variables are set (e.g. Architecture) +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): About to run Get-PSDLocalInfo" Get-PSDLocalInfo +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Deployroot is now $deployRoot" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): env:PSModulePath is now $env:PSModulePath" + +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): --------------------" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Checking if there is an in-progress task sequence" # Check for an in-progress task sequence $tsInProgress = $false -get-volume | ? {-not [String]::IsNullOrWhiteSpace($_.DriveLetter) } | ? {$_.DriveType -eq 'Fixed'} | ? {$_.DriveLetter -ne 'X'} | ? {Test-Path "$($_.DriveLetter):\_SMSTaskSequence\TSEnv.dat"} | % { +Get-Volume | ? {-not [String]::IsNullOrWhiteSpace($_.DriveLetter) } | ? {$_.DriveType -eq 'Fixed'} | ? {$_.DriveLetter -ne 'X'} | ? {Test-Path "$($_.DriveLetter):\_SMSTaskSequence\TSEnv.dat"} | % { # Found it, save the location - Write-Verbose "In-progress task sequence found at $($_.DriveLetter):\_SMSTaskSequence." + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): In-progress task sequence found at $($_.DriveLetter):\_SMSTaskSequence" $tsInProgress = $true $tsDrive = $_.DriveLetter # Restore the task sequence variables $variablesPath = Restore-PSDVariables - Write-Verbose "Restored variables from $variablesPath." + try + { + foreach($i in (Get-ChildItem -Path TSEnv:)) + { + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Property $($i.Name) is $($i.Value)" + } + + } + catch + { + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Unable to restore variables from $variablesPath." + Show-PSDInfo -Message "Unable to restore variables from $variablesPath." -Severity Error + Start-Process PowerShell -Wait + Exit 1 + } + + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Restored variables from $variablesPath." # Reconnect to the deployment share - Write-Verbose "Reconnecting to the deployment share at $($tsenv:DeployRoot)." + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Reconnecting to the deployment share at $($tsenv:DeployRoot)." if ($tsenv:UserDomain -ne "") { Get-PSDConnection -deployRoot $tsenv:DeployRoot -username "$($tsenv:UserDomain)\$($tsenv:UserID)" -password $tsenv:UserPassword @@ -69,6 +177,12 @@ get-volume | ? {-not [String]::IsNullOrWhiteSpace($_.DriveLetter) } | ? {$_.Driv } +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): --------------------" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): If a task sequence is in progress, resume it. Otherwise, start a new one" + +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Deployroot is now $deployRoot" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): env:PSModulePath is now $env:PSModulePath" + # If a task sequence is in progress, resume it. Otherwise, start a new one [Environment]::CurrentDirectory = "$($env:WINDIR)\System32" if ($tsInProgress) @@ -82,7 +196,7 @@ if ($tsInProgress) { $tsEngine = Get-PSDContent "Tools\$($tsenv:Architecture)" } - Write-Verbose "Task sequence engine located at $tsEngine." + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Task sequence engine located at $tsEngine." # Get full scripts location $scripts = Get-PSDContent -Content "Scripts" @@ -93,15 +207,17 @@ if ($tsInProgress) $env:PSModulePath = $env:PSModulePath + ";$modules" # Resume task sequence + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Deployroot is now $deployRoot" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): env:PSModulePath is now $env:PSModulePath" Stop-PSDLogging $result = Start-Process -FilePath "$tsEngine\TSMBootstrap.exe" -ArgumentList "/env:SAContinue" -Wait -Passthru } else { - Write-Verbose "No task sequence is in progress." + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): No task sequence is in progress." # Process bootstrap - Write-Verbose "Processing Bootstrap.ini" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Processing Bootstrap.ini" if ($env:SYSTEMDRIVE -eq "X:") { $mappingFile = "X:\Deploy\Tools\Modules\PSDGather\ZTIGather.xml" @@ -112,15 +228,257 @@ else $mappingFile = "$deployRoot\Scripts\ZTIGather.xml" Invoke-PSDRules -FilePath "$deployRoot\Control\Bootstrap.ini" -MappingFile $mappingFile } - $deployRoot = $tsenv:DeployRoot - Write-Verbose "New deploy root is $deployRoot." + + # Determine the Deployroot + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): --------------------" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Determine the Deployroot" + + # Check if we are deploying from media + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): --------------------" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Check if we are deploying from media" + + Get-Volume | ? {-not [String]::IsNullOrWhiteSpace($_.DriveLetter) } | ? {$_.DriveType -eq 'Fixed'} | ? {$_.DriveLetter -ne 'X'} | ? {Test-Path "$($_.DriveLetter):Deploy\Scripts\Media.tag"} | % { + # Found it, save the location + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Found Media Tag $($_.DriveLetter):Deploy\Scripts\Media.tag" + $tsDrive = $_.DriveLetter + $tsenv:DeployRoot = $tsDrive + ":\Deploy" + $tsenv:ResourceRoot = $tsDrive + ":\Deploy" + $tsenv:DeploymentMethod = "MEDIA" + + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): DeploymentMethod is $tsenv:DeploymentMethod, this solution does not currently support deploying from media, sorry, aborting" + Show-PSDInfo -Message "No deployroot set, this solution does not currently support deploying from media, aborting..." -Severity Error + Start-Process PowerShell -Wait + Break + } + + switch ($tsenv:DeploymentMethod) + { + 'MEDIA' + { + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): DeploymentMethod is $tsenv:DeploymentMethod, this solution does not currently support deploying from media, sorry, aborting" + Show-PSDInfo -Message "No deployroot set, this solution does not currently support deploying from media, aborting..." -Severity Error + Start-Process PowerShell -Wait + Break + } + Default + { + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): --------------------" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): We are deploying from Network, checking IP's," + + # Check Network + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Invoking DHCP refresh..." + Invoke-PSDexe -Executable ipconfig.exe -Arguments "/renew" | Out-Null + + $NICIPOK = $False + + $ipList = @() + $ipListv4 = @() + $macList = @() + $gwList = @() + Get-WmiObject -Class Win32_NetworkAdapterConfiguration -Filter "IPEnabled = 1" | % { + $_.IPAddress | % {$ipList += $_ } + $_.MacAddress | % {$macList += $_ } + if ($_.DefaultIPGateway) { + $_.DefaultIPGateway | % {$gwList += $_ } + } + } + $ipListv4 = $ipList | Where-Object Length -EQ 15 + + foreach($IPv4 in $ipListv4) + { + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Found IP address $IPv4" + } + + if (((Get-WmiObject -Class Win32_NetworkAdapterConfiguration -Filter "IPEnabled = 1").Index).count -ge 1) + { + $NICIPOK = $True + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): We have at least one network adapter with a IP address, we should be able to continue" + } + + + if($NICIPOK -ne $True) + { + $Message = "Sorry, it seems that you dont have a valid IP, aborting..." + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): $Message" + Show-PSDInfo -Message "$Message" -Severity Error + Start-Process PowerShell -Wait + break + } + + # Log if we are running APIPA as warning + # Log IP, Networkadapter name, if exist GW and DNS + # Return Network as deployment method, with Yes we have network + } + } + + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): --------------------" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Testing if we are using PSDdeployroots or not" + + if($tsenv:PSDDeployRoots -ne "") + { + $items = $tsenv:PSDDeployRoots.Split(",") + foreach($item in $items) + { + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Testing $item" + if ($item -ilike "http://*") + { + $ServerName = $item.Replace("http://","") | Split-Path + $Result = Test-PSDNetCon -Hostname $ServerName -Protocol HTTP + if(($Result) -ne $true) + { + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Unable to access $item using HTTP" + } + else + { + $tsenv:DeployRoot = $item + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Deployroot is now $tsenv:DeployRoot" + Break + } + } + if ($item -ilike "https://*") + { + $ServerName = $item.Replace("https://","") | Split-Path + $Result = Test-PSDNetCon -Hostname $ServerName -Protocol HTTPS + if(($Result) -ne $true) + { + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Unable to access $item using HTTPS" + } + else + { + $tsenv:DeployRoot = $item + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Deployroot is now $tsenv:DeployRoot" + Break + } + } + if ($item -like "\\*") + { + $ServerName = $item.Split("\\")[2] + $Result = Test-PSDNetCon -Hostname $ServerName -Protocol SMB + if(($Result) -ne $true) + { + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Unable to access $item using SMB" + } + else + { + $tsenv:DeployRoot = $item + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Deployroot is now $tsenv:DeployRoot" + Break + } + } + } + } + else + { + $deployRoot = $tsenv:DeployRoot + } + + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): --------------------" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Validate network route to $tsenv:DeployRoot" + + if(!($tsenv:DeployRoot -notlike $null -or "")) + { + $Message = "Since we are deploying from network, we should be able to access the deploymentshare, but we can't, please check your network." + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): $Message" + Show-PSDInfo -Message "$Message" -Severity Error + Start-Process PowerShell -Wait + Break + } + + + if($NICIPOK -eq $False) + { + if ($deployRoot -notlike $null -or "") + { + $Message = "Since we are deploying from network, we should have network access but we don't, check networking" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): $Message" + Show-PSDInfo -Message "$Message" -Severity Error + Start-Process PowerShell -Wait + Break + } + } + + # Validate network route to $deployRoot + if ($deployRoot -notlike $null -or "") + { + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): New deploy root is $deployRoot." + if ($deployRoot -ilike "http://*") + { + $ServerName = $deployRoot.Replace("http://","") | Split-Path + $Result = Test-PSDNetCon -Hostname $ServerName -Protocol HTTP + if(($Result) -ne $true) + { + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Unable to access $ServerName" + Show-PSDInfo -Message "Unable to access $ServerName, aborting..." -Severity Error + Start-Process PowerShell -Wait + Break + } + } + if ($deployRoot -like "\\*") + { + $ServerName = $deployRoot.Split("\\")[2] + $Result = Test-PSDNetCon -Hostname $ServerName -Protocol SMB -ErrorAction SilentlyContinue + if(($Result) -ne $true) + { + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Unable to access $ServerName" + Show-PSDInfo -Message "Unable to access $ServerName, aborting..." -Severity Error + Start-Process PowerShell -Wait + Break + } + } + } + else + { + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Deployroot is empty, this solution does not currently support deploying from media, sorry, aborting" + Show-PSDInfo -Message "No deployroot set, this solution does not currently support deploying from media, aborting..." -Severity Error + Start-Process PowerShell -Wait + Break + } + + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): New deploy root is $deployRoot." Get-PSDConnection -DeployRoot $tsenv:DeployRoot -Username "$tsenv:UserDomain\$tsenv:UserID" -Password $tsenv:UserPassword + # Set time on client + If($tsenv:DeploymentMethod -ne "MEDIA") + { + if ($deployRoot -like "\\*") + { + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): About to run net time \\$ServerName /set /y" + net time \\$ServerName /set /y + } + if ($deployRoot -ilike "http://*") + { + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): About to run Get-PSDNtpTime -NTPServer time.windows.com" + Get-PSDNtpTime -NTPServer time.windows.com + } + } + + $Time = Get-Date + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Computertime is set to $Time" + # Process CustomSettings.ini $control = Get-PSDContent -Content "Control" - Write-Verbose "Processing CustomSettings.ini" + + #verify access to "$control\CustomSettings.ini" + if((Test-path -Path "$control\CustomSettings.ini") -ne $true) + { + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Unable to access $control\CustomSettings.ini" + Show-PSDInfo -Message "Unable to access $control\CustomSettings.ini, aborting..." -Severity Error + Start-Process PowerShell -Wait + Break + } + + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Processing CustomSettings.ini" Invoke-PSDRules -FilePath "$control\CustomSettings.ini" -MappingFile $mappingFile + if($tsenv:EventService -notlike $null -or "") + { + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Eventlogging is enabled" + } + else + { + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Eventlogging is not enabled" + } + # Get full scripts location $scripts = Get-PSDContent -Content "Scripts" $env:ScriptRoot = $scripts @@ -130,21 +488,39 @@ else $env:PSModulePath = $env:PSModulePath + ";$modules" # Process wizard + $tsenv:TaskSequenceID = "" if ($tsenv:SkipWizard -ine "YES") { $result = Show-PSDWizard "$scripts\PSDWizard.xaml" - if ($result -eq $false) + + if ($result.DialogResult -eq $false) { - Write-Verbose "Cancelling." + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Cancelling, aborting..." + Show-PSDInfo -Message "Cancelling, aborting..." -Severity Information Stop-PSDLogging Clear-PSDInformation - exit 0 + Start-Process PowerShell -Wait + Exit 0 } } + + If ($tsenv:TaskSequenceID -eq "") + { + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): No TaskSequence selected, aborting..." + Show-PSDInfo -Message "No TaskSequence selected, aborting..." -Severity Information + Stop-PSDLogging + Clear-PSDInformation + Start-Process PowerShell -Wait + Exit 0 + } + if ($tsenv:OSDComputerName -eq "") { $tsenv:OSDComputerName = $env:COMPUTERNAME } + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): --------------------" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Find the task sequence engine" + # Find the task sequence engine if (Test-Path -Path "X:\Deploy\Tools\$($tsenv:Architecture)\tsmbootstrap.exe") { @@ -154,58 +530,200 @@ else { $tsEngine = Get-PSDContent "Tools\$($tsenv:Architecture)" } - Write-Verbose "Task sequence engine located at $tsEngine." + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Task sequence engine located at $tsEngine." + + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): tsenv:Phase is now: $tsenv:Phase" + + if($BootfromWinPE -eq $true) + { + $tsenv:Phase = "PREINSTALL" + if($tsenv:DeploymentType -eq "") + { + $tsenv:DeploymentType = "NEWCOMPUTER" + } + } + + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): tsenv:Phase is now: $tsenv:Phase" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): tsenv:DeploymentType is now: $tsenv:DeploymentType" + + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Saving Variables" + Save-PSDVariables + +# if($tsenv:Phase -eq "") +# { +# $tsenv:phase = "Initialization" +# Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Phase is set to $tsenv:phase" +# Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Save all the current variables for later use" +# Save-PSDVariables +# } + + + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): --------------------" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Start the task sequence" # Start task sequence $variablesPath = Save-PSDVariables Copy-Item -Path $variablesPath -Destination $tsEngine -Force Copy-Item -Path "$control\$($tsenv:TaskSequenceID)\ts.xml" -Destination $tsEngine -Force + #Update TS.XML before using it, changing workbench specific .WSF scripts to PowerShell to avoid issues + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Update ts.xml before using it, changing workbench specific .WSF scripts to PowerShell to avoid issues" + + $TSxml = "$tsEngine\ts.xml" + (Get-Content -Path $TSxml).replace('cscript.exe "%SCRIPTROOT%\ZTIDrivers.wsf"','PowerShell.exe -file "%SCRIPTROOT%\PSDDrivers.ps1"') | Set-Content -Path $TSxml + (Get-Content -Path $TSxml).replace('cscript.exe "%SCRIPTROOT%\ZTIGather.wsf"','PowerShell.exe -file "%SCRIPTROOT%\PSDGather.ps1"') | Set-Content -Path $TSxml + (Get-Content -Path $TSxml).replace('cscript.exe "%SCRIPTROOT%\ZTIValidate.wsf"','PowerShell.exe -file "%SCRIPTROOT%\PSDValidate.ps1"') | Set-Content -Path $TSxml + (Get-Content -Path $TSxml).replace('cscript.exe "%SCRIPTROOT%\ZTIBIOSCheck.wsf"','PowerShell.exe -file "%SCRIPTROOT%\PSDTBA.ps1"') | Set-Content -Path $TSxml + (Get-Content -Path $TSxml).replace('cscript.exe "%SCRIPTROOT%\ZTIDiskpart.wsf"','PowerShell.exe -file "%SCRIPTROOT%\PSDPartition.ps1"') | Set-Content -Path $TSxml + (Get-Content -Path $TSxml).replace('cscript.exe "%SCRIPTROOT%\ZTIUserState.wsf" /capture','PowerShell.exe -file "%SCRIPTROOT%\PSDTBA.ps1" /capture') | Set-Content -Path $TSxml + (Get-Content -Path $TSxml).replace('cscript.exe "%SCRIPTROOT%\ZTIBackup.wsf"','PowerShell.exe -file "%SCRIPTROOT%\PSDTBA.ps1"') | Set-Content -Path $TSxml + (Get-Content -Path $TSxml).replace('cscript.exe "%SCRIPTROOT%\ZTISetVariable.wsf"','PowerShell.exe -file "%SCRIPTROOT%\PSDSetVariable.ps1"') | Set-Content -Path $TSxml + (Get-Content -Path $TSxml).replace('cscript.exe "%SCRIPTROOT%\ZTINextPhase.wsf"','PowerShell.exe -file "%SCRIPTROOT%\PSDTBA.ps1"') | Set-Content -Path $TSxml + (Get-Content -Path $TSxml).replace('cscript.exe "%SCRIPTROOT%\LTIApply.wsf"','PowerShell.exe -file "%SCRIPTROOT%\PSDApplyOS.ps1"') | Set-Content -Path $TSxml + (Get-Content -Path $TSxml).replace('cscript.exe "%SCRIPTROOT%\ZTIWinRE.wsf"','PowerShell.exe -file "%SCRIPTROOT%\PSDTBA.ps1"') | Set-Content -Path $TSxml + (Get-Content -Path $TSxml).replace('cscript.exe "%SCRIPTROOT%\ZTIPatches.wsf"','PowerShell.exe -file "%SCRIPTROOT%\PSDTBA.ps1"') | Set-Content -Path $TSxml + (Get-Content -Path $TSxml).replace('cscript.exe "%SCRIPTROOT%\ZTINextPhase.wsf"','PowerShell.exe -file "%SCRIPTROOT%\PSDTBA.ps1"') | Set-Content -Path $TSxml + (Get-Content -Path $TSxml).replace('cscript.exe "%SCRIPTROOT%\ZTIApplications.wsf"','PowerShell.exe -file "%SCRIPTROOT%\PSDTBA.ps1"') | Set-Content -Path $TSxml + (Get-Content -Path $TSxml).replace('cscript.exe "%SCRIPTROOT%\ZTIWindowsUpdate.wsf"','PowerShell.exe -file "%SCRIPTROOT%\PSDTBA.ps1"') | Set-Content -Path $TSxml + (Get-Content -Path $TSxml).replace('cscript.exe "%SCRIPTROOT%\ZTIBde.wsf"','PowerShell.exe -file "%SCRIPTROOT%\PSDTBA.ps1"') | Set-Content -Path $TSxml + (Get-Content -Path $TSxml).replace('cscript.exe "%SCRIPTROOT%\ZTIBDE.wsf"','PowerShell.exe -file "%SCRIPTROOT%\PSDTBA.ps1"') | Set-Content -Path $TSxml + (Get-Content -Path $TSxml).replace('cscript.exe "%SCRIPTROOT%\ZTIGroups.wsf"','PowerShell.exe -file "%SCRIPTROOT%\PSDTBA.ps1"') | Set-Content -Path $TSxml + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Saving a copy of the updated TS.xml" + Copy-Item -Path $tsEngine\ts.xml -Destination "$(Get-PSDLocalDataPath)\" + + # Check for unsupported variables + if(!(($tsenv:SLShareDynamicLogging -eq "") -or ($tsenv:SLShareDynamicLogging -eq $Null))) + { + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): $tsenv:SLShareDynamicLogging is currently not supported" + } + + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Deployroot is now $deployRoot" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): env:PSModulePath is now $env:PSModulePath" + Write-PSDEvent -MessageID 41016 -severity 4 -Message "PSD beginning deployment" Stop-PSDLogging $result = Start-Process -FilePath "$tsEngine\TSMBootstrap.exe" -ArgumentList "/env:SAStart" -Wait -Passthru } +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Deployroot is now $deployRoot" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): env:PSModulePath is now $env:PSModulePath" + # Make sure variables.dat is in the current local directory if (Test-Path "$(Get-PSDLocalDataPath)\Variables.dat") { - Write-Verbose "Variables.dat found in the correct location, $(Get-PSDLocalDataPath)\Variables.dat, no need to copy." + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Variables.dat found in the correct location, $(Get-PSDLocalDataPath)\Variables.dat, no need to copy." } else { - Write-Verbose "Copying Variables.dat to the current location, $(Get-PSDLocalDataPath)\Variables.dat." + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Copying Variables.dat to the current location, $(Get-PSDLocalDataPath)\Variables.dat." Copy-Item $variablesPath "$(Get-PSDLocalDataPath)\" } +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Deployroot is now $deployRoot" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): env:PSModulePath is now $env:PSModulePath" + # Process the exit code from the task sequence Start-PSDLogging Switch ($result.ExitCode) { 0 { - Write-Verbose "SUCCESS!" - Stop-PSDLogging + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): SUCCESS!" + Write-PSDEvent -MessageID 41015 -severity 4 -Message "PSD deployment completed successfully." + + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Reset HKLM:\Software\Microsoft\Deployment 4" + Get-ItemProperty "HKLM:\Software\Microsoft\Deployment 4" | Remove-Item -Force -Recurse + + #Checking for FinalSummary + if(!($tsenv:SkipFinalSummary -eq "YES")) + { + Show-PSDInfo -Message "OSD SUCCESS!" -Severity Information + } + + # TODO Reboot for finishaction + if($tsenv:finishaction -eq "Reboot" -or "Restart") + { + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): TODO Reboot for finishaction" + } + Clear-PSDInformation + Stop-PSDLogging + exit 0 } -2147021886 { - Write-Verbose "REBOOT!" - Stop-PSDLogging + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): REBOOT!" if ($env:SYSTEMDRIVE -eq "X:") { # Exit with a zero return code and let Windows PE reboot + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Exit with a zero return code and let Windows PE reboot" + Stop-PSDLogging exit 0 } else { # In full OS, need to initiate a reboot - Restart-Computer -Force - Start-Sleep -Seconds 120 + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): In full OS, need to initiate a reboot" + + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Execute Save-PSDVariables" + Save-PSDVariables + + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Findig out where the tools folder is..." + $Tools = Get-PSDContent -Content "Tools\$($tsenv:Architecture)" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Tools is now $Tools" + + $Executable = "regsvr32.exe" + $Arguments = "/u /s $tools\tscore.dll" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): About to run: $Executable $Arguments" + $return = Invoke-PSDEXE -Executable $Executable -Arguments $Arguments + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Exitcode: $return" + + $Executable = "$Tools\TSProgressUI.exe" + $Arguments = "/Unregister" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): About to run: $Executable $Arguments" + $return = Invoke-PSDEXE -Executable $Executable -Arguments $Arguments + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Exitcode: $return" + + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Restart, see you on the other side... (Shutdown.exe /r /t 30 /f)" + + #Restart-Computer -Force + Shutdown.exe /r /t 30 /f + + Stop-PSDLogging + exit 0 } } default { # Exit with a non-zero return code - Write-Verbose "Task sequence failed, rc = $($result.ExitCode)" - Stop-PSDLogging + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Task sequence failed, rc = $($result.ExitCode)" + + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Reset HKLM:\Software\Microsoft\Deployment 4" + Get-ItemProperty "HKLM:\Software\Microsoft\Deployment 4" -ErrorAction SilentlyContinue | Remove-Item -Force -Recurse + + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Reset HKLM:\Software\Microsoft\SMS" + Get-ItemProperty "HKLM:\Software\Microsoft\SMS" -ErrorAction SilentlyContinue | Remove-Item -Force -Recurse + + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Findig out where the tools folder is..." + $Tools = Get-PSDContent -Content "Tools\$($tsenv:Architecture)" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Tools is now $Tools" + + $Executable = "regsvr32.exe" + $Arguments = "/u /s $tools\tscore.dll" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): About to run: $Executable $Arguments" + $return = Invoke-PSDEXE -Executable $Executable -Arguments $Arguments + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Exitcode: $return" + + $Executable = "$Tools\TSProgressUI.exe" + $Arguments = "/Unregister" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): About to run: $Executable $Arguments" + $return = Invoke-PSDEXE -Executable $Executable -Arguments $Arguments + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Exitcode: $return" + Clear-PSDInformation + Stop-PSDLogging + + #Invoke-PSDInfoGather + Write-PSDEvent -MessageID 41014 -severity 1 -Message "PSD deployment failed, Return Code is $($result.ExitCode)" + Show-PSDInfo -Message "Task sequence failed, Return Code is $($result.ExitCode)" -Severity Error + exit $result.ExitCode } } diff --git a/Scripts/PSDTBA.ps1 b/Scripts/PSDTBA.ps1 new file mode 100644 index 0000000..bec61ea --- /dev/null +++ b/Scripts/PSDTBA.ps1 @@ -0,0 +1,28 @@ +# // *************************************************************************** +# // +# // PowerShell Deployment for MDT +# // +# // File: PSDTemplate.ps1 +# // +# // Purpose: Apply the specified operating system. +# // +# // +# // *************************************************************************** + +param ( + +) + +# Load core modules +Import-Module Microsoft.BDD.TaskSequenceModule -Scope Global +Import-Module PSDUtility +Import-Module PSDDeploymentShare + +$verbosePreference = "Continue" + +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Load core modules" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Deployroot is now $($tsenv:DeployRoot)" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): env:PSModulePath is now $env:PSModulePath" + +#Notify +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): The built in VB Script has been replaced by the script, however, the function the VB Script would have done is not yet implemented, sorry, working on this" diff --git a/Scripts/PSDTemplate.ps1 b/Scripts/PSDTemplate.ps1 new file mode 100644 index 0000000..8bae13c --- /dev/null +++ b/Scripts/PSDTemplate.ps1 @@ -0,0 +1,22 @@ +# // *************************************************************************** +# // +# // PowerShell Deployment for MDT +# // +# // File: PSDTemplate.ps1 +# // +# // Purpose: Apply the specified operating system. +# // +# // +# // *************************************************************************** + +param ( + +) + +# Load core modules +Import-Module PSDUtility +Import-Module PSDDeploymentShare +$verbosePreference = "Continue" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Load core modules" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Deployroot is now $deployRoot" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): env:PSModulePath is now $env:PSModulePath" diff --git a/Scripts/PSDUserState.ps1 b/Scripts/PSDUserState.ps1 index e762553..cb107d3 100644 --- a/Scripts/PSDUserState.ps1 +++ b/Scripts/PSDUserState.ps1 @@ -1,3 +1,29 @@ -# -# PSDUserState.ps1 -# +# // *************************************************************************** +# // +# // PowerShell Deployment for MDT +# // +# // File: PSDUserState.ps1 +# // +# // Purpose: Start or continue a PSD task sequence. +# // +# // +# // *************************************************************************** + +param ( + $Action +) + +# Load core modules +Import-Module Microsoft.BDD.TaskSequenceModule -Scope Global +Import-Module PSDUtility + +$verbosePreference = "Continue" + +#Write-Verbose -Message "$($MyInvocation.MyCommand.Name): Load core modules" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Load core modules" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Deployroot is now $($tsenv:DeployRoot)" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): env:PSModulePath is now $env:PSModulePath" + +# TODO Action response +#Write-Verbose -Message "$($MyInvocation.MyCommand.Name): TODO Action response $Action" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): TODO Action response $Action" diff --git a/Scripts/PSDUtility.psm1 b/Scripts/PSDUtility.psm1 index 1ce9ee2..7ddb8f6 100644 --- a/Scripts/PSDUtility.psm1 +++ b/Scripts/PSDUtility.psm1 @@ -1,161 +1,948 @@ -# // *************************************************************************** -# // -# // PowerShell Deployment for MDT -# // -# // File: PSDUtility.psd1 -# // -# // Purpose: General utility routines useful for all PSD scripts. -# // -# // *************************************************************************** +# // *************************************************************************** +# // +# // PowerShell Deployment for MDT +# // +# // File: PSDUtility.psd1 +# // +# // Purpose: General utility routines useful for all PSD scripts. +# // +# // *************************************************************************** + +#$VerbosePreference = "SilentlyContinue" + +Import-Module Microsoft.BDD.TaskSequenceModule -Scope Global -Force -Verbose -ErrorAction Stop + +#$verbosePreference = "Continue" + +$global:psuDataPath = "" +$caller = Split-Path -Path $MyInvocation.PSCommandPath -Leaf + +function Get-PSDLocalDataPath +{ + param ( + [switch] $move + ) + # Return the cached local data path if possible + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Return the cached local data path if possible" + if ($global:psuDataPath -ne "" -and (-not $move)) + { + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): global:psuDataPath is $psuDataPath, testing access" + if (Test-Path $global:psuDataPath) + { + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Returning data $psuDataPath" + Return $global:psuDataPath + } + } + + # Always prefer the OS volume + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Always prefer the OS volume" + + $localPath = "" + + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): localpath is $localPath" + if ($tsenv:OSVolumeGuid -ne "") + { + if ($tsenv:OSVolumeGuid -eq "MBR") + { + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): tsenv:OSVolumeGuid is now $($tsenv:OSVolumeGuid)" + if($tsenv:OSVersion -eq "WinPE") + { + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): tsenv:OSVersion is now $($tsenv:OSVersion)" + + # If the OS volume GUID is not set, we use the fake volume guid value "MBR" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Get the OS image details (MBR)" + + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Using OS volume from tsenv:OSVolume: $($tsenv:OSVolume)." + + $localPath = "$($tsenv:OSVolume):\MININT" + + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): localPath is now $localPath" + } + else + { + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): tsenv:OSVersion is now $($tsenv:OSVersion)" + # If the OS volume GUID is not set, we use the fake volume guid value "MBR" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Get the OS image details (MBR)" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Using OS volume from env:SystemDrive $($env:SystemDrive)." + $localPath = "$($env:SystemDrive)\MININT" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): localPath is now $localPath" + } + } + else + { + # If the OS volume GUID is set, we should use that volume (UEFI) + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Get the OS image details (UEFI)" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Checking for OS volume using $($tsenv:OSVolumeGuid)." + Get-Volume | ? { $_.UniqueID -like "*$($tsenv:OSVolumeGuid)*" } | % { + $localPath = "$($_.DriveLetter):\MININT" + } + } + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): localpath is now $localPath" + } + + if ($localPath -eq "") + { + # Look on all other volumes + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Look on all other volumes" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Checking other volumes for a MININT folder." + Get-Volume | ? {-not [String]::IsNullOrWhiteSpace($_.DriveLetter) } | ? {$_.DriveType -eq 'Fixed'} | ? {$_.DriveLetter -ne 'X'} | ? {Test-Path "$($_.DriveLetter):\MININT"} | Select-Object -First 1 | % { + $localPath = "$($_.DriveLetter):\MININT" + } + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): localpath is now $localPath" + } + + # Not found on any drive, create one on the current system drive + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Not found on any drive, create one on the current system drive" + if ($localPath -eq "") + { + $localPath = "$($env:SYSTEMDRIVE)\MININT" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): localpath is now $localPath" + } + + # Create the MININT folder if it doesn't exist + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Create the MININT folder if it doesn't exist" + if ((Test-Path $localPath) -eq $false) { + New-Item -ItemType Directory -Force -Path $localPath | Out-Null + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): localpath is now $localPath" + } + + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): localpath set to $localPath" + $global:psuDataPath = $localPath + return $localPath +} + +function Initialize-PSDFolder +{ + Param( + $folderPath + ) + + if ((Test-Path $folderPath) -eq $false) { + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Creating $folderPath" + New-Item -ItemType Directory -Force -Path $folderPath | Out-Null + } +} + +function Start-PSDLogging +{ + $logPath = "$(Get-PSDLocalDataPath)\SMSOSD\OSDLOGS" + Initialize-PSDfolder $logPath + Start-Transcript "$logPath\$caller.transcript.log" -Append + Write-Verbose -Message "$($MyInvocation.MyCommand.Name): Logging transcript to $logPath\$caller.transcript.log" + + #Writing to CMtrace file + #Set PSDLogPath + $PSDLogFile = "$($($caller).Substring(0,$($caller).Length-4)).log" + $Global:PSDLogPath = "$logPath\$PSDLogFile" + + #Create logfile + if (!(Test-Path $Global:PSDLogPath)) + { + ## Create the log file + New-Item $Global:PSDLogPath -Type File | Out-Null + } + + Write-Verbose -Message "$($MyInvocation.MyCommand.Name): Logging CMtrace logs to $Global:PSDLogPath" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Logging CMtrace logs to $Global:PSDLogPath" +} + +function Stop-PSDLogging +{ + Stop-Transcript + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Stop Transcript Logging" +} + +Function Write-PSDLog +{ + param ( + [Parameter(Mandatory = $true)] + [string]$Message, + + [Parameter()] + [ValidateSet(1, 2, 3)] + [string]$LogLevel = 1 + ) + + # Don't log any lines containing the word password + if($Message -like '*password*') + { + $Message = "" + } + + # PSDDebug settings + if($tsenv:PSDDebug -eq "YES") + { + $WriteToScreen = $true + } + + #check if we have a logpath set + if($Global:PSDLogPath -ne $null) + { + if (!(Test-Path -Path $Global:PSDLogPath)) + { + ## Create the log file + New-Item $Global:PSDLogPath -Type File | Out-Null + } + + $TimeGenerated = "$(Get-Date -Format HH:mm:ss).$((Get-Date).Millisecond)+000" + $Line = '' + $LineFormat = $Message, $TimeGenerated, (Get-Date -Format MM-dd-yyyy), "$($MyInvocation.ScriptName | Split-Path -Leaf):$($MyInvocation.ScriptLineNumber)", $LogLevel + $Line = $Line -f $LineFormat + + #Log to scriptfile + Add-Content -Value $Line -Path $Global:PSDLogPath + + #Log to networkshare + if($DynLogging -eq $true) + { + Add-Content -Value $Line -Path $PSDDynLogPath -ErrorAction SilentlyContinue + } + + #Log to masterfile + Add-Content -Value $Line -Path (($Global:PSDLogPath | Split-Path) + "\PSD.log") + } + + if($writetoscreen -eq $true){ + switch ($LogLevel) + { + '1'{ + Write-Verbose -Message $Message + } + '2'{ + Write-Warning -Message $Message + } + '3'{ + Write-Error -Message $Message + } + Default {} + } + } +} + Start-PSDLogging + +function Save-PSDVariables +{ + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Save-PSDVariables" + $v = [xml]"" + Get-ChildItem TSEnv: | % { + $element = $v.CreateElement("var") + $element.SetAttribute("name", $_.Name) | Out-Null + $element.AppendChild($v.createCDATASection($_.Value)) | Out-Null + $v.DocumentElement.AppendChild($element) | Out-Null + } + $path = "$(Get-PSDLocaldataPath)\Variables.dat" + $v.Save($path) + return $path +} + +function Restore-PSDVariables +{ + $path = "$(Get-PSDLocaldataPath)\Variables.dat" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Restore-PSDVariables from $path" + if (Test-Path -Path $path) { + [xml] $v = Get-Content -Path $path + $v | Select-Xml -Xpath "//var" | % { Set-Item tsenv:$($_.Node.name) -Value $_.Node.'#cdata-section' } + } + return $path +} + +function Clear-PSDInformation +{ + # Create a folder for the logs + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Create a folder for the logs" + $logDest = "$($env:SystemRoot)\Temp\DeploymentLogs" + Initialize-PSDFolder $logDest + + # Process each volume looking for MININT folders + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Process each volume looking for MININT folders" + Get-Volume | ? {-not [String]::IsNullOrWhiteSpace($_.DriveLetter) } | ? {$_.DriveType -eq 'Fixed'} | ? {$_.DriveLetter -ne 'X'} | ? {Test-Path "$($_.DriveLetter):\MININT"} | % { + $localPath = "$($_.DriveLetter):\MININT" + + # Copy PSD logs + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Copy PSD logs" + if (Test-Path "$localPath\SMSOSD\OSDLOGS") + { + Copy-Item "$localPath\SMSOSD\OSDLOGS\*" $logDest -Force + } + + # Copy Panther logs + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Copy Panther logs" + if (Test-Path "$localPath\Logs") + { + & xcopy $env:SystemRoot\Panther $logDest /s /e /v /d /y /i | Out-Null + } + + # Copy SMSTS log + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Copy SMSTS log" + if (Test-Path "$localPath\Logs") + { + Copy-Item -Path $env:LOCALAPPDATA\temp\smstslog\smsts.log -Destination $logDest + } + + # Check if DEVRunCleanup is set to NO + if ($($tsenv:DEVRunCleanup) -eq "NO") + { + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): tsenv:DEVRunCleanup is now $tsenv:DEVRunCleanup" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Cleanup will not remove MININT or Drivers folder" + } + else + { + + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): tsenv:DEVRunCleanup is now $tsenv:DEVRunCleanup" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Cleanup will remove MININT and Drivers folder" + + # Remove the MININT folder + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Remove the MININT folder" + try + { + Remove-Item "$localPath" -Recurse -Force + } + catch + { + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Unable to completely remove $localPath." + } + + # Remove the Drivers folder + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Remove the Drivers folder" + try + { + Remove-Item "$($env:Systemdrive + "\Drivers")" -Recurse -Force + } + catch + { + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Unable to completely remove $($env:Systemdrive + "\Drivers")." + } + } + } + + # Cleanup start folder + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Cleanup start folder" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Removing link to re-run $PSCommandPath from the all users Startup folder" + + # Remove shortcut to PSDStart.ps1 if it exists + $allUsersStartup = [Environment]::GetFolderPath('CommonStartup') + $linkPath = "$allUsersStartup\PSDStartup.lnk" + if(Test-Path $linkPath) + { + $Null = Get-Item -Path $linkPath | Remove-Item -Force + } + + # Cleanup AutoLogon + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Cleanup AutoLogon" + + $Null = New-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon' -Name AutoAdminLogon -Value 0 -Force + $Null = New-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon' -Name DefaultUserName -Value "" -Force + $Null = New-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon' -Name DefaultPassword -Value "" -Force + + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): AutoLogon has been removed" +} + +function Copy-PSDFolder +{ + param ( + [Parameter(Mandatory=$True,Position=1)] + [string] $source, + [Parameter(Mandatory=$True,Position=2)] + [string] $destination + ) + + $s = $source.TrimEnd("\") + $d = $destination.TrimEnd("\") + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Copying folder $source to $destination using XCopy" + & xcopy $s $d /s /e /v /d /y /i | Out-Null +} + +function Test-PSDNetCon +{ + Param + ( + $Hostname, + $Protocol + ) + + +switch ($Protocol) +{ + SMB + { + $Port = 445 + } + HTTP + { + $Port = 80 + } + HTTPS + { + $Port = 443 + } + WINRM + { + $Port = 5985 + } + Default + { + exit + } +} + try + { + $ips = [System.Net.Dns]::GetHostAddresses($hostname) | Where-Object AddressFamily -EQ InterNetwork | Select-Object IPAddressToString -ExpandProperty IPAddressToString + if($ips.GetType().Name -eq "Object[]") + { + $ips + } + } + catch + { + Write-Verbose "Possibly $hostname is wrong hostname or IP" + $ips = "NA" + } + + foreach($ip in $ips) + { + $TcpClient = New-Object Net.Sockets.TcpClient + try + { + Write-Verbose "Testing $ip,$port" + $TcpClient.Connect($ip,$port) + } + catch + { + } + + if($TcpClient.Connected) + { + $TcpClient.Close() + $Result = $true + Return $Result + Break + } + else + { + $Result = $false + } + } + Return $Result +} + +Function Get-PSDDriverInfo +{ + Param + ( + $Path = $Driver.FullName + ) + + #Get filename + $InfName = $Path | Split-Path -Leaf + + $Pattern = 'DriverVer' + $Content = Get-Content -Path $Path + #$DriverVer = $Content | Select-String -Pattern $Pattern + $DriverVer = (($Content | Select-String -Pattern $Pattern -CaseSensitive) -replace '.*=(.*)','$1') -replace ' ','' -replace ',','-' -split "-" + + $DriverVersion = ($DriverVer[1] -split ";")[0] + + $Pattern = 'Class' + $Content = Get-Content -Path $Path + $Class = ((($Content | Select-String -Pattern $Pattern) -notlike "ClassGUID*"))[0] -replace " ","" -replace '.*=(.*)','$1' -replace '"','' + + + $Provider = ($Content | Select-String '^\s*Provider\s*=.*') -replace '.*=(.*)','$1' + if ($Provider.Length -eq 0) { + $Provider = "" + } + elseif($Provider.Length -gt 0 -And $Provider -is [system.array]) { + if ($Provider.Length -gt 1 -And $Provider[0].Trim(" ").StartsWith("%")) { + $Provider = $Provider[1]; + } else { + $Provider = $Provider[0] + } + } + $Provider = $Provider.Trim(' ') + + if ($Provider.StartsWith("%")) { + $Provider = $Provider.Trim('%') + $Manufacter = ($Content | Select-String "^$Provider\s*=") -replace '.*=(.*)','$1' + } + else { + $Manufacter = "" + } + + if ($Manufacter.Length -eq 0) { + $Manufacter = $Provider + } elseif ($Manufacter.Length -gt 0 -And $Manufacter -is [system.array]) { + if ($Manufacter.Length -gt 1 -And $Manufacter[0].Trim(" ").StartsWith("%")) { + $Manufacter = $Manufacter[1]; + } + else { + $Manufacter = $Manufacter[0]; + } + } + $Manufacter = $Manufacter.Trim(' ').Trim('"') + + + + $HashTable = [Ordered]@{ + Name = $InfName + Manufacturer = $Manufacter + Class = $Class + Date = $DriverVer[0] + Version = $DriverVersion + } + + New-Object -TypeName psobject -Property $HashTable +} Function Show-PSDInfo +{ + Param + ( + $Message, + [ValidateSet("Information","Warning","Error")] + $Severity = "Information", + $OSDComputername, + $Deployroot + ) + +$File = { +Param +( + $Message, + $Severity = "Information", + $OSDComputername, + $Deployroot +) + +switch ($Severity) +{ + 'Error' + { + $BackColor = "salmon" + $Label1Text = "Error" + } + 'Warning' + { + $BackColor = "yellow" + $Label1Text = "Warning" + } + 'Information' + { + $BackColor = "#F0F0F0" + $Label1Text = "Information" + } + Default + { + $BackColor = "#F0F0F0" + $Label1Text = "Information" + } +} + +Get-WmiObject Win32_ComputerSystem | % { + $Manufacturer = $_.Manufacturer + $Model = $_.Model + $Memory = [int] ($_.TotalPhysicalMemory / 1024 / 1024) +} + +Get-WmiObject Win32_ComputerSystemProduct | % { + $UUID = $_.UUID +} + +Get-WmiObject Win32_BaseBoard | % { + $Product = $_.Product + $SerialNumber = $_.SerialNumber +} + +try{Get-SecureBootUEFI -Name SetupMode | Out-Null ; $BIOSUEFI = "UEFI"}catch{$BIOSUEFI = "BIOS"} + +Get-WmiObject Win32_SystemEnclosure | % { + $AssetTag = $_.SMBIOSAssetTag.Trim() + if ($_.ChassisTypes[0] -in "8", "9", "10", "11", "12", "14", "18", "21") { $ChassiType = "Laptop"} + if ($_.ChassisTypes[0] -in "3", "4", "5", "6", "7", "15", "16") { $ChassiType = "Desktop"} + if ($_.ChassisTypes[0] -in "23") { $ChassiType = "Server"} + if ($_.ChassisTypes[0] -in "34", "35", "36") { $ChassiType = "Small Form Factor"} + if ($_.ChassisTypes[0] -in "13", "31", "32", "30") { $ChassiType = "Tablet"} +} + +$ipList = @() +$macList = @() +$gwList = @() +Get-WmiObject -Class Win32_NetworkAdapterConfiguration -Filter "IPEnabled = 1" | % { + $_.IPAddress | % {$ipList += $_ } + $_.MacAddress | % {$macList += $_ } + if ($_.DefaultIPGateway) { + $_.DefaultIPGateway | % {$gwList += $_ } + } +} +$IPAddress = $ipList +$MacAddress = $macList +$DefaultGateway = $gwList + +try +{ + Add-Type -AssemblyName System.Windows.Forms -IgnoreWarnings + [System.Windows.Forms.Application]::EnableVisualStyles() +} +catch [System.UnauthorizedAccessException] { + # This should never happen, but we're catching if it does anyway. + Start-Process PowerShell -ArgumentList { + Write-warning -Message 'Access denied when trying to load required assemblies, cannot display the summary window.' + Pause + } -Wait + exit 1 +} +catch [System.Exception] { + # This should never happen either, but we're catching if it does anyway. + Start-Process PowerShell -ArgumentList { + Write-warning -Message 'Unable to load required assemblies, cannot display the summary window.' + Pause + } -Wait + exit 1 +} + +$Form = New-Object system.Windows.Forms.Form +$Form.ClientSize = '600,390' +$Form.text = "PSD" +$Form.StartPosition = "CenterScreen" +$Form.BackColor = $BackColor +$Form.TopMost = $true +$Form.Icon = [System.Drawing.Icon]::ExtractAssociatedIcon($PSHome + "\powershell.exe") + +$Label1 = New-Object system.Windows.Forms.Label +$Label1.text = "$Label1Text" +$Label1.AutoSize = $true +$Label1.width = 25 +$Label1.height = 10 +$Label1.location = New-Object System.Drawing.Point(25,10) +$Label1.Font = 'Segoe UI,14' + +$Label2 = New-Object system.Windows.Forms.Label +$Label2.text = "OSDComputername: $OSDComputername" +$Label2.AutoSize = $true +$Label2.width = 25 +$Label2.height = 10 +$Label2.location = New-Object System.Drawing.Point(25,180) +$Label2.Font = 'Segoe UI,10' + +$Label3 = New-Object system.Windows.Forms.Label +$Label3.text = "DeployRoot: $Deployroot" +$Label3.AutoSize = $true +$Label3.width = 25 +$Label3.height = 10 +$Label3.location = New-Object System.Drawing.Point(25,200) +$Label3.Font = 'Segoe UI,10' + +$Label4 = New-Object system.Windows.Forms.Label +$Label4.text = "Model: $Model" +$Label4.AutoSize = $true +$Label4.width = 25 +$Label4.height = 10 +$Label4.location = New-Object System.Drawing.Point(25,220) +$Label4.Font = 'Segoe UI,10' + +$Label5 = New-Object system.Windows.Forms.Label +$Label5.text = "Manufacturer: $Manufacturer" +$Label5.AutoSize = $true +$Label5.width = 25 +$Label5.height = 10 +$Label5.location = New-Object System.Drawing.Point(25,240) +$Label5.Font = 'Segoe UI,10' + +$Label6 = New-Object system.Windows.Forms.Label +$Label6.text = "Memory(MB): $Memory" +$Label6.AutoSize = $true +$Label6.width = 25 +$Label6.height = 10 +$Label6.location = New-Object System.Drawing.Point(25,260) +$Label6.Font = 'Segoe UI,10' + +$Label7 = New-Object system.Windows.Forms.Label +$Label7.text = "BIOS/UEFI: $BIOSUEFI" +$Label7.AutoSize = $true +$Label7.width = 25 +$Label7.height = 10 +$Label7.location = New-Object System.Drawing.Point(25,280) +$Label7.Font = 'Segoe UI,10' + +$Label8 = New-Object system.Windows.Forms.Label +$Label8.text = "SerialNumber: $SerialNumber" +$Label8.AutoSize = $true +$Label8.width = 25 +$Label8.height = 10 +$Label8.location = New-Object System.Drawing.Point(25,300) +$Label8.Font = 'Segoe UI,10' + +$Label9 = New-Object system.Windows.Forms.Label +$Label9.text = "UUID: $UUID" +$Label9.AutoSize = $true +$Label9.width = 25 +$Label9.height = 10 +$Label9.location = New-Object System.Drawing.Point(25,320) +$Label9.Font = 'Segoe UI,10' + +$Label10 = New-Object system.Windows.Forms.Label +$Label10.text = "ChassiType: $ChassiType" +$Label10.AutoSize = $true +$Label10.width = 25 +$Label10.height = 10 +$Label10.location = New-Object System.Drawing.Point(25,340) +$Label10.Font = 'Segoe UI,10' + +$TextBox1 = New-Object system.Windows.Forms.TextBox +$TextBox1.multiline = $True +$TextBox1.width = 550 +$TextBox1.height = 100 +$TextBox1.location = New-Object System.Drawing.Point(25,60) +$TextBox1.Font = 'Segoe UI,12' +$TextBox1.Text = $Message +$TextBox1.ReadOnly = $True + +$Button1 = New-Object system.Windows.Forms.Button +$Button1.text = "Ok" +$Button1.width = 60 +$Button1.height = 30 +$Button1.location = New-Object System.Drawing.Point(500,300) +$Button1.Font = 'Segoe UI,12' + +$Form.controls.AddRange(@($Label1,$Label2,$Label3,$Label4,$Label5,$Label6,$Label7,$Label8,$Label9,$Label10,$TextBox1,$Button1)) + +$Button1.Add_Click({ Ok }) + +function Ok (){$Form.close()} + +[void]$Form.ShowDialog() +} + $ScriptFile = $env:TEMP + "\Show-PSDInfo.ps1" + $File | Out-File -Width 255 -FilePath $ScriptFile + + if(($OSDComputername -eq "") -or ($OSDComputername -eq $null)){$OSDComputername = $env:COMPUTERNAME} + if(($Deployroot -eq "") -or ($Deployroot -eq $null)){$Deployroot = "NA"} + + Start-Process -FilePath PowerShell.exe -ArgumentList $ScriptFile, "'$Message'", $Severity, $OSDComputername, $Deployroot + + #$ScriptFile = $env:TEMP + "\Show-PSDInfo.ps1" + #$RunFile = $env:TEMP + "\Show-PSDInfo.cmd" + #$File | Out-File -Width 255 -FilePath $ScriptFile + #Set-Content -Path $RunFile -Force -Value "PowerShell.exe -File $ScriptFile -Message ""$Message"" -Severity $Severity -OSDComputername $OSDComputername -Deployroot $Deployroot" + #Start-Process -FilePath $RunFile +} + +Function Get-PSDInputFromScreen +{ + Param + ( + $Header, + $Message, + + [ValidateSet("Ok","Yes")] + $ButtonText + ) + + Add-Type -AssemblyName System.Windows.Forms + Add-Type -AssemblyName System.Drawing + + $form = New-Object System.Windows.Forms.Form + $form.Text = $Header + $form.Size = New-Object System.Drawing.Size(400,200) + $form.StartPosition = 'CenterScreen' + + $Button1 = New-Object System.Windows.Forms.Button + $Button1.Location = New-Object System.Drawing.Point(290,110) + $Button1.Size = New-Object System.Drawing.Size(80,30) + $Button1.Text = $ButtonText + $Button1.DialogResult = [System.Windows.Forms.DialogResult]::OK + $form.AcceptButton = $Button1 + $form.Controls.Add($Button1) + + $Label1 = New-Object System.Windows.Forms.Label + $Label1.Location = New-Object System.Drawing.Point(10,20) + $Label1.Size = New-Object System.Drawing.Size(300,20) + $Label1.Text = $Message + $form.Controls.Add($Label1) + + $textBox = New-Object System.Windows.Forms.TextBox + $textBox.Location = New-Object System.Drawing.Point(10,40) + $textBox.Size = New-Object System.Drawing.Size(360,20) + $form.Controls.Add($textBox) + + $form.Topmost = $true + $form.Add_Shown({$textBox.Select()}) + $result = $form.ShowDialog() + + Return $textBox.Text +} + +Function Invoke-PSDHelper +{ + Param( + $MDTDeploySharePath, + $UserName, + $Password + ) + + #Connect + & net use $MDTDeploySharePath $Password /USER:$UserName + + #Import Env + Import-Module Microsoft.BDD.TaskSequenceModule -Scope Global -Force -Verbose + Import-Module PSDUtility -Force -Verbose + Import-Module PSDDeploymentShare -Force -Verbose + Import-Module PSDGather -Force -Verbose + + dir tsenv: | Out-File "$($env:SystemDrive)\DumpVars.log" + Get-Content -Path "$($env:SystemDrive)\DumpVars.log" +} + +Function Invoke-PSDEXE +{ + [CmdletBinding(SupportsShouldProcess=$true)] + + param( + [parameter(mandatory=$true,position=0)] + [ValidateNotNullOrEmpty()] + [string] + $Executable, + + [parameter(mandatory=$false,position=1)] + [string] + $Arguments + ) + + if($Arguments -eq "") + { + Write-Verbose "Running Start-Process -FilePath $Executable -ArgumentList $Arguments -NoNewWindow -Wait -Passthru" + $ReturnFromEXE = Start-Process -FilePath $Executable -NoNewWindow -Wait -Passthru + }else{ + Write-Verbose "Running Start-Process -FilePath $Executable -ArgumentList $Arguments -NoNewWindow -Wait -Passthru" + $ReturnFromEXE = Start-Process -FilePath $Executable -ArgumentList $Arguments -NoNewWindow -Wait -Passthru + } + Write-Verbose "Returncode is $($ReturnFromEXE.ExitCode)" + Return $ReturnFromEXE.ExitCode +} + +Function Set-PSDCommandWindowsSize +{ + <# + .Synopsis + Resets the size of the current console window + .Description + Set-myConSize resets the size of the current console window. By default, it + sets the windows to a height of 40 lines, with a 3000 line buffer, and sets the + the width and width buffer to 120 characters. + .Example + Set-myConSize + Restores the console window to 120x40 + .Example + Set-myConSize -Height 30 -Width 180 + Changes the current console to a height of 30 lines and a width of 180 characters. + .Parameter Height + The number of lines to which to set the current console. The default is 40 lines. + .Parameter Width + The number of characters to which to set the current console. Default is 120. Also sets the buffer to the same value + .Inputs + [int] + [int] + .Notes + Author: Charlie Russel + Copyright: 2017 by Charlie Russel + : Permission to use is granted but attribution is appreciated + Initial: 28 April, 2017 (cpr) + ModHist: + : + #> + [CmdletBinding()] + Param( + [Parameter(Mandatory=$False,Position=0)] + [int] + $Height = 40, + [Parameter(Mandatory=$False,Position=1)] + [int] + $Width = 120 + ) + $Console = $host.ui.rawui + $Buffer = $Console.BufferSize + $ConSize = $Console.WindowSize -Import-Module Microsoft.BDD.TaskSequenceModule -Scope Global -$caller = Split-Path -Path $MyInvocation.PSCommandPath -Leaf -$verbosePreference = "Continue" -$global:psuDataPath = "" - -function Get-PSDLocalDataPath -{ - param ( - [switch] $move - ) - - # Return the cached local data path if possible - if ($global:psuDataPath -ne "" -and (-not $move)) - { - if (Test-Path $global:psuDataPath) - { - return $global:psuDataPath - } - } - - # Always prefer the OS volume - $localPath = "" - if ($tsenv:OSVolumeGuid -ne "") - { - # If the OS volume GUID is set, we should use that volume - Write-Verbose "Checking for OS volume using $($tsenv:OSVolumeGuid)." - Get-Volume | ? { $_.UniqueID -like "*$($tsenv:OSVolumeGuid)*" } | % { - $localPath = "$($_.DriveLetter):\MININT" - } - } - - if ($localPath -eq "") - { - # Look on all other volumes - Write-Verbose "Checking other volumes for a MININT folder." - get-volume | ? {-not [String]::IsNullOrWhiteSpace($_.DriveLetter) } | ? {$_.DriveType -eq 'Fixed'} | ? {$_.DriveLetter -ne 'X'} | ? {Test-Path "$($_.DriveLetter):\MININT"} | Select-Object -First 1 | % { - $localPath = "$($_.DriveLetter):\MININT" - } - } - - # Not found on any drive, create one on the current system drive - if ($localPath -eq "") - { - $localPath = "$($env:SYSTEMDRIVE)\MININT" - } - - # Create the MININT folder if it doesn't exist - if ((Test-Path $localPath) -eq $false) { - New-Item -ItemType Directory -Force -Path $localPath | Out-Null - } - - $global:psuDataPath = $localPath - return $localPath -} - -function Initialize-PSDFolder -{ - Param( - $folderPath - ) - - if ((Test-Path $folderPath) -eq $false) { - New-Item -ItemType Directory -Force -Path $folderPath | Out-Null - } -} - -function Start-PSDLogging -{ - $logPath = "$(Get-PSDLocalDataPath)\Logs" - Initialize-PSDfolder $logPath - Start-Transcript "$logPath\$caller.log" -Append -} - -function Stop-PSDLogging -{ - Stop-Transcript -} - -Start-PSDLogging - -function Save-PSDVariables -{ - $v = [xml]"" - Get-ChildItem TSEnv: | % { - $element = $v.CreateElement("var") - $element.SetAttribute("name", $_.Name) | Out-Null - $element.AppendChild($v.createCDATASection($_.Value)) | Out-Null - $v.DocumentElement.AppendChild($element) | Out-Null - } - $path = "$(Get-PSDLocaldataPath)\Variables.dat" - $v.Save($path) - return $path -} - -function Restore-PSDVariables -{ - $path = "$(Get-PSDLocaldataPath)\Variables.dat" - if (Test-Path $path) { - [xml] $v = Get-Content $path - $v | Select-Xml -Xpath "//var" | % { Set-Item tsenv:$($_.Node.name) -Value $_.Node.'#cdata-section' } + # If the Buffer is wider than the new console setting, first reduce the buffer, then do the resize + If ($Buffer.Width -gt $Width ) { + $ConSize.Width = $Width + $Console.WindowSize = $ConSize } - return $path -} - -function Clear-PSDInformation -{ - # TEMP: Don't clean up - return - - # Create a folder for the logs - $logDest = "$($env:SystemRoot)\Temp\DeploymentLogs" - Initialize-PSDFolder $logDest - - # Process each volume looking for MININT folders - get-volume | ? {-not [String]::IsNullOrWhiteSpace($_.DriveLetter) } | ? {$_.DriveType -eq 'Fixed'} | ? {$_.DriveLetter -ne 'X'} | ? {Test-Path "$($_.DriveLetter):\MININT"} | % { - - $localPath = "$($_.DriveLetter):\MININT" - - # Copy any logs - if (Test-Path "$localPath\Logs") - { - Copy-Item "$localPath\Logs\*" $logDest -Force - } - - # Remove the MININT folder - try - { - Remove-Item "$localPath" -Recurse -Force - } - catch - { - Write-Verbose "Unable to completely remove $localPath." - } - } - -} - -function Copy-PSDFolder -{ - param ( - [Parameter(Mandatory=$True,Position=1)] - [string] $source, - [Parameter(Mandatory=$True,Position=2)] - [string] $destination - ) - - $s = $source.TrimEnd("\") - $d = $destination.TrimEnd("\") - Write-Verbose "Copying folder $source to $destination using XCopy" - & xcopy $s $d /s /e /v /d /y /i | Out-Null -} + $Buffer.Width = $Width + $ConSize.Width = $Width + $Buffer.Height = 3000 + $Console.BufferSize = $Buffer + $ConSize = $Console.WindowSize + $ConSize.Width = $Width + $ConSize.Height = $Height + $Console.WindowSize = $ConSize +} + +Function Get-PSDNtpTime +{ +( + [String]$NTPServer +) + +# From https://www.madwithpowershell.com/2016/06/getting-current-time-from-ntp-service.html + +# Build NTP request packet. We'll reuse this variable for the response packet +$NTPData = New-Object byte[] 48 # Array of 48 bytes set to zero +$NTPData[0] = 27 # Request header: 00 = No Leap Warning; 011 = Version 3; 011 = Client Mode; 00011011 = 27 + +# Open a connection to the NTP service +$Socket = New-Object Net.Sockets.Socket ( 'InterNetwork', 'Dgram', 'Udp' ) +$Socket.SendTimeOut = 2000 # ms +$Socket.ReceiveTimeOut = 2000 # ms +$Socket.Connect( $NTPServer, 123 ) + +# Make the request +$Null = $Socket.Send( $NTPData ) +$Null = $Socket.Receive( $NTPData ) + +# Clean up the connection +$Socket.Shutdown( 'Both' ) +$Socket.Close() + +# Extract relevant portion of first date in result (Number of seconds since "Start of Epoch") +$Seconds = [BitConverter]::ToUInt32( $NTPData[43..40], 0 ) + +# Add them to the "Start of Epoch", convert to local time zone, and return +( [datetime]'1/1/1900' ).AddSeconds( $Seconds ).ToLocalTime() +} + +Function Write-PSDEvent +{ + param( + $MessageID, + $severity, + $Message + ) + + if($tsenv:EventService -eq ""){return} + + # a Deployment has started (EventID 41016) + # a Deployment completed successfully (EventID 41015) + # a Deployment failed (EventID 41014) + # an error occurred (EventID 3) + # a warning occurred (EventID 2) + + if($tsenv:LTIGUID -eq "") + { + $LTIGUID = ([guid]::NewGuid()).guid + New-Item -Path TSEnv: -Name "LTIGUID" -Value "$LTIGUID" -Force + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): tsenv:LTIGUID is now: $tsenv:LTIGUID" + Save-PSDVariables + } + + $MacAddress = $tsenv:MacAddress001 + $Lguid = $tsenv:LTIGUID + $id = $tsenv:UUID + $vmhost = 'NA' + $ComputerName = $tsenv:OSDComputerName + + $CurrentStep = $tsenv:_SMSTSNextInstructionPointer + if($CurrentStep -eq ""){$CurrentStep = '0'} + + $TotalSteps = $tsenv:_SMSTSInstructionTableSize + if($TotalSteps -eq ""){$TotalSteps= '0'} + + $Return = Invoke-WebRequest "$tsenv:EventService/MDTMonitorEvent/PostEvent?uniqueID=$Lguid&computerName=$ComputerName&messageID=$messageID&severity=$severity&stepName=$CurrentStep&totalSteps=$TotalSteps&id=$id,$macaddress&message=$Message&dartIP=&dartPort=&dartTicket=&vmHost=$vmhost&vmName=$ComputerName" -UseBasicParsing +} \ No newline at end of file diff --git a/Scripts/PSDValidate.ps1 b/Scripts/PSDValidate.ps1 new file mode 100644 index 0000000..d03b5c0 --- /dev/null +++ b/Scripts/PSDValidate.ps1 @@ -0,0 +1,87 @@ +# // *************************************************************************** +# // +# // PowerShell Deployment for MDT +# // +# // File: PSDTemplate.ps1 +# // +# // Purpose: Apply the specified operating system. +# // +# // +# // *************************************************************************** + +param ( + +) + +# Load core modules +Import-Module Microsoft.BDD.TaskSequenceModule -Scope Global +Import-Module PSDUtility +Import-Module PSDDeploymentShare + +$verbosePreference = "Continue" + +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Load core modules" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Deployroot is now $($tsenv:DeployRoot)" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): env:PSModulePath is now $env:PSModulePath" + +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): tsenv:ImageSize $($tsenv:ImageSize)" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): tsenv:ImageProcessorSpeed $($tsenv:ImageProcessorSpeed)" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): tsenv:ImageMemory $($tsenv:ImageMemory)" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): tsenv:VerifyOS $($tsenv:VerifyOS)" + +<# + '//---------------------------------------------------------------------------- + '// Abort if this is a server OS + '//---------------------------------------------------------------------------- +#> + +If($TSEnv:DeploymentType -eq "REFRESH") +{ + If ($TSEnv:VerifyOS -eq "CLIENT") + { + If($TSEnv:IsServerOS -eq "TRUE") + { + $Message = "ERROR - Attempting to deploy a client operating system to a machine running a server operating system." + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): $Message" -LogLevel 3 + Show-PSDInfo -Message $Message -Severity Error + Start-Process PowerShell -Wait + Break + } + } + + If ($TSEnv:VerifyOS -eq "SERVER") + { + If($TSEnv:IsServerOS -eq "FALSE") + { + $Message = "ERROR - Attempting to deploy a server operating system to a machine running a client operating system." + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): $Message" -LogLevel 3 + Show-PSDInfo -Message $Message -Severity Error + Start-Process PowerShell -Wait + Break + } + } +} + +<# + '//---------------------------------------------------------------------------- + '// Abort if "OSInstall" flag is set to something other than Y or YES + '//---------------------------------------------------------------------------- +#> + + If($TSEnv:OSInstall -eq "Y" -or "YES") + { + $Message = "OSInstall flag is $TSEnv:OSInstall , install is allowed." + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): $Message" -LogLevel 1 + } + else + { + $Message = "OSInstall flag is NOT set to Y or YES, abort." + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): $Message" -LogLevel 3 + Show-PSDInfo -Message $Message -Severity Error + Start-Process PowerShell -Wait + Break + } + + +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Save all the current variables for later use" +Save-PSDVariables diff --git a/Scripts/PSDWindowsUpdate.ps1 b/Scripts/PSDWindowsUpdate.ps1 new file mode 100644 index 0000000..c3c5196 --- /dev/null +++ b/Scripts/PSDWindowsUpdate.ps1 @@ -0,0 +1,1492 @@ +# // *************************************************************************** +# // +# // PowerShell Deployment for MDT +# // +# // File: PSDWindowsUpdate.ps1 +# // +# // Purpose: Apply the specified operating system. +# // +# // +# // *************************************************************************** + +param ( + +) + +# Load core modules +Import-Module PSDUtility +Import-Module PSDDeploymentShare +$verbosePreference = "Continue" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Load core modules" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Deployroot is now $deployRoot" +Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): env:PSModulePath is now $env:PSModulePath" + +function Find-WindowsUpdateList { +<# + +.SYNOPSIS +Create a sheduled task to run powershell script that find available or installed windows updates through COM object. + +.DESCRIPTION +Create a sheduled task to run powershell script that find available or installed windows updates through COM object. + +.ROLE +Readers + +.PARAMETER searchCriteria + "IsInstalled = 0": available updates, "IsInstalled = 1": installed updates + +.PARAMETER sessionId + session Id used to identify different query instance + +.PARAMETER serverSelection + update service server + +#> + +Param( +[Parameter(Mandatory = $true)] +[string]$searchCriteria, +[Parameter(Mandatory = $true)] +[string]$sessionId, +[Parameter(Mandatory = $true)] +[int16]$serverSelection +) + +#PowerShell script to run. In some cases, you may need use back quote (`) to treat some characters (eg. double/single quote, special escape sequence) literally. +$Script = @' +function GenerateSearchHash($searchResults) { + foreach ($searchResult in $searchResults){ + foreach ($KBArticleID in $searchResult.KBArticleIDs) { + $KBID = 'KB' + $KBArticleID + if ($KBArticleID -ne $null -and -Not $searchHash.ContainsKey($KBID)) { + $searchHash.Add($KBID, ($searchResult | Microsoft.PowerShell.Utility\Select-Object msrcSeverity, title, IsMandatory)) + } + } + } +} + +function GenerateHistoryHash($historyResults) { + foreach ($historyResult in $historyResults){ + $KBID = ([regex]::match($historyResult.Title,'KB(\d+)')).Value.ToUpper() + if ($KBID -ne $null -and $KBID -ne '') { + $title = $historyResult.Title.Trim() + + if (-Not $historyHash.ContainsKey($KBID)) { + $historyHash.Add($KBID, ($historyResult | Microsoft.PowerShell.Utility\Select-Object ResultCode, Date, Title)) + } elseif (($historyHash[$KBID].Title -eq $null -or $historyHash[$KBID].Title -eq '') -and ($title -ne $null -or $title.Length -gt 0)) { + #If the previous entry did not have a title and this item has one, update it + $historyHash[$KBID] = $historyResult | Microsoft.PowerShell.Utility\Select-Object ResultCode, Date, $title + } + } + } +} + +$objSession = New-Object -ComObject "Microsoft.Update.Session" +$objSearcher = $objSession.CreateUpdateSearcher() +$objSearcher.ServerSelection = $serverSelection +$objResults = $objSearcher.Search($searchCriteria) + +$result = New-Object Collections.ArrayList + +if ($searchCriteria -eq "IsInstalled=1") { + $searchHash = @{} + GenerateSearchHash($objResults.Updates) + + $historyCount = $objSearcher.GetTotalHistoryCount() + $historyResults = $objSearcher.QueryHistory(0, $historyCount) + + $historyHash = @{} + GenerateHistoryHash($historyResults) + + $installedItems = Get-Hotfix + foreach ($installedItem in $installedItems) { + $resultItem = $installedItem | Microsoft.PowerShell.Utility\Select-Object HotFixID, InstalledBy + $title = $installedItem.Description + ' (' + $resultItem.HotFixID + ')' + $installDate = $installedItem.InstalledOn + + $titleMatch = $null + + $searchMatch = $searchHash.Item($installedItem.HotFixID) + if ($searchMatch -ne $null) { + $titleMatch = $searchMatch.title + $resultItem | Add-Member -MemberType NoteProperty -Name "msrcSeverity" -Value $searchMatch.msrcSeverity + $resultItem | Add-Member -MemberType NoteProperty -Name "IsMandatory" -Value $searchMatch.IsMandatory + } + + $historyMatch = $historyHash.Item($installedItem.HotFixID) + if ($historyMatch -ne $null) { + $resultItem | Add-Member -MemberType NoteProperty -Name "installState" -Value $historyMatch.ResultCode + if ($titleMatch -eq $null -or $titleMatch -eq '') { + # If there was no matching title in searchMatch + $titleMatch = $historyMatch.title + } + + $installDate = $historyMatch.Date + } + + if ($titleMatch -ne $null -or $titleMatch -ne '') { + $title = $titleMatch + } + + $resultItem | Add-Member -MemberType NoteProperty -Name "title" -Value $title + $resultItem | Add-Member -MemberType NoteProperty -Name "installDate" -Value $installDate + + $result.Add($resultItem) + } +} else { + foreach ($objResult in $objResults.Updates) { + $resultItem = $objResult | Microsoft.PowerShell.Utility\Select-Object msrcSeverity, title, IsMandatory + $result.Add($resultItem) + } +} + +if(Test-Path $ResultFile) +{ + Remove-Item $ResultFile +} + +$result | ConvertTo-Json -depth 10 | Out-File $ResultFile +'@ + +#Pass parameters to script and generate script file in localappdata folder +$timeStamp = Get-Date -Format FileDateTimeUniversal +# use both ps sessionId and timestamp for file/task prefix so that multiple instances won't delete others' files and tasks +$fileprefix = "_PS"+ $sessionId + "_Time" + $timeStamp +$ResultFile = $env:TEMP + "\Find-Updates-result" + $fileprefix + ".json" +$Script = '$searchCriteria = ' + "'$searchCriteria';" + '$ResultFile = ' + "'$ResultFile';" + '$serverSelection =' + "'$serverSelection';" + $Script +$ScriptFile = $env:TEMP + "\Find-Updates" + $fileprefix + ".ps1" +$Script | Out-File $ScriptFile +if (-Not(Test-Path $ScriptFile)) { + $message = "Failed to create file:" + $ScriptFile + Write-Error $message + return #If failed to create script file, no need continue just return here +} + +#Create a scheduled task +$TaskName = "SMEWindowsUpdateFindUpdates" + $fileprefix + +$User = [Security.Principal.WindowsIdentity]::GetCurrent() +$Role = (New-Object Security.Principal.WindowsPrincipal $User).IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator) +$arg = "-NoProfile -NoLogo -NonInteractive -ExecutionPolicy Bypass -File $ScriptFile" +if(!$Role) +{ + Write-Warning "To perform some operations you must run an elevated Windows PowerShell console." +} + +$Scheduler = New-Object -ComObject Schedule.Service + +#Try to connect to schedule service 3 time since it may fail the first time +for ($i=1; $i -le 3; $i++) +{ + Try + { + $Scheduler.Connect() + Break + } + Catch + { + if($i -ge 3) + { + Write-EventLog -LogName Application -Source "SME Windows Updates Find Updates" -EntryType Error -EventID 1 -Message "Can't connect to Schedule service" + Write-Error "Can't connect to Schedule service" -ErrorAction Stop + } + else + { + Start-Sleep -s 1 + } + } +} + +$RootFolder = $Scheduler.GetFolder("\") +#Delete existing task +if($RootFolder.GetTasks(0) | Where-Object {$_.Name -eq $TaskName}) +{ + Write-Debug("Deleting existing task" + $TaskName) + $RootFolder.DeleteTask($TaskName,0) +} + +$Task = $Scheduler.NewTask(0) +$RegistrationInfo = $Task.RegistrationInfo +$RegistrationInfo.Description = $TaskName +$RegistrationInfo.Author = $User.Name + +$Triggers = $Task.Triggers +$Trigger = $Triggers.Create(7) #TASK_TRIGGER_REGISTRATION: Starts the task when the task is registered. +$Trigger.Enabled = $true + +$Settings = $Task.Settings +$Settings.Enabled = $True +$Settings.StartWhenAvailable = $True +$Settings.Hidden = $False + +$Action = $Task.Actions.Create(0) +$Action.Path = "powershell" +$Action.Arguments = $arg + +#Tasks will be run with the highest privileges +$Task.Principal.RunLevel = 1 + +#Start the task to run in Local System account. 6: TASK_CREATE_OR_UPDATE +$RootFolder.RegisterTaskDefinition($TaskName, $Task, 6, "SYSTEM", $Null, 1) | Out-Null +#Wait for running task finished +$RootFolder.GetTask($TaskName).Run(0) | Out-Null +while($Scheduler.GetRunningTasks(0) | Where-Object {$_.Name -eq $TaskName}) +{ + Start-Sleep -s 1 +} + +#Clean up +$RootFolder.DeleteTask($TaskName,0) +Remove-Item $ScriptFile +#Return result +if(Test-Path $ResultFile) +{ + $result = Get-Content -Raw -Path $ResultFile | ConvertFrom-Json + Remove-Item $ResultFile + return $result +} + +} +## [END] Find-WindowsUpdateList ## +function Get-AutomaticUpdatesOptions { +<# + +.SYNOPSIS +Script that get windows update automatic update options from registry key. + +.DESCRIPTION +Script that get windows update automatic update options from registry key. + +.ROLE +Readers + +#> + +Import-Module Microsoft.PowerShell.Management + +# If there is AUOptions, return it, otherwise return NoAutoUpdate value +$option = Get-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" -Name "AUOptions" -ErrorVariable myerror -ErrorAction SilentlyContinue +if ($option -ne $null) { + return $option.AUOptions +} elseif ($myerror) { + $option = Get-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" -Name "NoAutoUpdate" -ErrorVariable myerror -ErrorAction SilentlyContinue + if ($option -ne $null) { + return $option.NoAutoUpdate + } elseif ($myerror) { + $option = 0 # not defined + } +} +return $option + +} +## [END] Get-AutomaticUpdatesOptions ## +function Get-MicrosoftMonitoringAgentStatus { +<# + +.SYNOPSIS +Script that returns if Microsoft Monitoring Agent is running or not. + +.DESCRIPTION +Script that returns if Microsoft Monitoring Agent is running or not. + +.ROLE +Readers + +#> + +Import-Module Microsoft.PowerShell.Management + +$status = Get-Service -Name HealthService -ErrorAction SilentlyContinue +if ($status -eq $null) { + # which means no such service is found. + @{ Installed=$false; Running=$false } +} elseif ($status.Status -eq "Running") { + @{ Installed=$true; Running=$true } +} else { + @{ Installed=$true; Running=$false } +} + +} +## [END] Get-MicrosoftMonitoringAgentStatus ## +function Get-WindowsUpdateInstallerStatus { +<# + +.SYNOPSIS +Script that check scheduled task for install updates is still running or not. + +.DESCRIPTION + Script that check scheduled task for install updates is still running or not. Notcied that using the following COM object has issue: when install-WUUpdates task is running, the busy status return false; + but right after the task finished, it returns true. + +.ROLE +Readers + +#> + +Import-Module ScheduledTasks + +$TaskName = "SMEWindowsUpdateInstallUpdates" +$ScheduledTask = Get-ScheduledTask | Microsoft.PowerShell.Utility\Select-Object TaskName, State | Where-Object {$_.TaskName -eq $TaskName} +if ($ScheduledTask -ne $Null -and $ScheduledTask.State -eq 4) { # Running + return $True +} else { + return $False +} + +} +## [END] Get-WindowsUpdateInstallerStatus ## +function Install-WindowsUpdates { +<# + +.SYNOPSIS +Create a scheduled task to run a powershell script file to installs all available windows updates through ComObject, restart the machine if needed. + +.DESCRIPTION +Create a scheduled task to run a powershell script file to installs all available windows updates through ComObject, restart the machine if needed. +This is a workaround since CreateUpdateDownloader() and CreateUpdateInstaller() methods can't be called from a remote computer - E_ACCESSDENIED. +More details see https://msdn.microsoft.com/en-us/library/windows/desktop/aa387288(v=vs.85).aspx + +.ROLE +Administrators + +.PARAMETER restartTime + The user-defined time to restart after update (Optional). + +.PARAMETER serverSelection + update service server + +#> + +param ( + [Parameter(Mandatory = $false)] + [String] + $restartTime, + [Parameter(Mandatory = $true)] + [int16]$serverSelection) + +$Script = @' +$objServiceManager = New-Object -ComObject 'Microsoft.Update.ServiceManager'; +$objSession = New-Object -ComObject 'Microsoft.Update.Session'; +$objSearcher = $objSession.CreateUpdateSearcher(); +$objSearcher.ServerSelection = $serverSelection; +$serviceName = 'Windows Update'; +$search = 'IsInstalled = 0'; +$objResults = $objSearcher.Search($search); +$Updates = $objResults.Updates; +$FoundUpdatesToDownload = $Updates.Count; + +$NumberOfUpdate = 1; +$objCollectionDownload = New-Object -ComObject 'Microsoft.Update.UpdateColl'; +$updateCount = $Updates.Count; +Foreach($Update in $Updates) +{ + Write-Progress -Activity 'Downloading updates' -Status `"[$NumberOfUpdate/$updateCount]` $($Update.Title)`" -PercentComplete ([int]($NumberOfUpdate/$updateCount * 100)); + $NumberOfUpdate++; + Write-Debug `"Show` update` to` download:` $($Update.Title)`" ; + Write-Debug 'Accept Eula'; + $Update.AcceptEula(); + Write-Debug 'Send update to download collection'; + $objCollectionTmp = New-Object -ComObject 'Microsoft.Update.UpdateColl'; + $objCollectionTmp.Add($Update) | Out-Null; + + $Downloader = $objSession.CreateUpdateDownloader(); + $Downloader.Updates = $objCollectionTmp; + Try + { + Write-Debug 'Try download update'; + $DownloadResult = $Downloader.Download(); + } <#End Try#> + Catch + { + If($_ -match 'HRESULT: 0x80240044') + { + Write-Warning 'Your security policy do not allow a non-administator identity to perform this task'; + } <#End If $_ -match 'HRESULT: 0x80240044'#> + + Return + } <#End Catch#> + + Write-Debug 'Check ResultCode'; + Switch -exact ($DownloadResult.ResultCode) + { + 0 { $Status = 'NotStarted'; } + 1 { $Status = 'InProgress'; } + 2 { $Status = 'Downloaded'; } + 3 { $Status = 'DownloadedWithErrors'; } + 4 { $Status = 'Failed'; } + 5 { $Status = 'Aborted'; } + } <#End Switch#> + + If($DownloadResult.ResultCode -eq 2) + { + Write-Debug 'Downloaded then send update to next stage'; + $objCollectionDownload.Add($Update) | Out-Null; + } <#End If $DownloadResult.ResultCode -eq 2#> +} + +$ReadyUpdatesToInstall = $objCollectionDownload.count; +Write-Verbose `"Downloaded` [$ReadyUpdatesToInstall]` Updates` to` Install`" ; +If($ReadyUpdatesToInstall -eq 0) +{ + Return; +} <#End If $ReadyUpdatesToInstall -eq 0#> + +$NeedsReboot = $false; +$NumberOfUpdate = 1; + +<#install updates#> +Foreach($Update in $objCollectionDownload) +{ + Write-Progress -Activity 'Installing updates' -Status `"[$NumberOfUpdate/$ReadyUpdatesToInstall]` $($Update.Title)`" -PercentComplete ([int]($NumberOfUpdate/$ReadyUpdatesToInstall * 100)); + Write-Debug 'Show update to install: $($Update.Title)'; + + Write-Debug 'Send update to install collection'; + $objCollectionTmp = New-Object -ComObject 'Microsoft.Update.UpdateColl'; + $objCollectionTmp.Add($Update) | Out-Null; + + $objInstaller = $objSession.CreateUpdateInstaller(); + $objInstaller.Updates = $objCollectionTmp; + + Try + { + Write-Debug 'Try install update'; + $InstallResult = $objInstaller.Install(); + } <#End Try#> + Catch + { + If($_ -match 'HRESULT: 0x80240044') + { + Write-Warning 'Your security policy do not allow a non-administator identity to perform this task'; + } <#End If $_ -match 'HRESULT: 0x80240044'#> + + Return; + } #End Catch + + If(!$NeedsReboot) + { + Write-Debug 'Set instalation status RebootRequired'; + $NeedsReboot = $installResult.RebootRequired; + } <#End If !$NeedsReboot#> + $NumberOfUpdate++; +} <#End Foreach $Update in $objCollectionDownload#> + +if($NeedsReboot){ + <#Restart immediately#> + $waitTime = 0 + if($restartTime) { + <#Restart at given time#> + $waitTime = [decimal]::round(((Get-Date $restartTime) - (Get-Date)).TotalSeconds); + if ($waitTime -lt 0 ) { + $waitTime = 0 + } + } + Shutdown -r -t $waitTime -c "SME installing Windows updates"; +} +'@ + +#Pass parameters to script and generate script file in localappdata folder +if ($restartTime){ + $Script = '$restartTime = ' + "'$restartTime';" + $Script +} +$Script = '$serverSelection =' + "'$serverSelection';" + $Script + +$ScriptFile = $env:LocalAppData + "\Install-Updates.ps1" +$Script | Out-File $ScriptFile +if (-Not(Test-Path $ScriptFile)) { + $message = "Failed to create file:" + $ScriptFile + Write-Error $message + return #If failed to create script file, no need continue just return here +} + +#Create a scheduled task +$TaskName = "SMEWindowsUpdateInstallUpdates" + +$User = [Security.Principal.WindowsIdentity]::GetCurrent() +$Role = (New-Object Security.Principal.WindowsPrincipal $User).IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator) +$arg = "-NoProfile -NoLogo -NonInteractive -ExecutionPolicy Bypass -File $ScriptFile" +if(!$Role) +{ + Write-Warning "To perform some operations you must run an elevated Windows PowerShell console." +} + +$Scheduler = New-Object -ComObject Schedule.Service + +#Try to connect to schedule service 3 time since it may fail the first time +for ($i=1; $i -le 3; $i++) +{ + Try + { + $Scheduler.Connect() + Break + } + Catch + { + if($i -ge 3) + { + Write-EventLog -LogName Application -Source "SME Windows Updates Install Updates" -EntryType Error -EventID 1 -Message "Can't connect to Schedule service" + Write-Error "Can't connect to Schedule service" -ErrorAction Stop + } + else + { + Start-Sleep -s 1 + } + } +} + +$RootFolder = $Scheduler.GetFolder("\") +#Delete existing task +if($RootFolder.GetTasks(0) | Where-Object {$_.Name -eq $TaskName}) +{ + Write-Debug("Deleting existing task" + $TaskName) + $RootFolder.DeleteTask($TaskName,0) +} + +$Task = $Scheduler.NewTask(0) +$RegistrationInfo = $Task.RegistrationInfo +$RegistrationInfo.Description = $TaskName +$RegistrationInfo.Author = $User.Name + +$Triggers = $Task.Triggers +$Trigger = $Triggers.Create(7) #TASK_TRIGGER_REGISTRATION: Starts the task when the task is registered. +$Trigger.Enabled = $true + +$Settings = $Task.Settings +$Settings.Enabled = $True +$Settings.StartWhenAvailable = $True +$Settings.Hidden = $False + +$Action = $Task.Actions.Create(0) +$Action.Path = "powershell" +$Action.Arguments = $arg + +#Tasks will be run with the highest privileges +$Task.Principal.RunLevel = 1 + +#Start the task to run in Local System account. 6: TASK_CREATE_OR_UPDATE +$RootFolder.RegisterTaskDefinition($TaskName, $Task, 6, "SYSTEM", $Null, 1) | Out-Null +#Wait for running task finished +$RootFolder.GetTask($TaskName).Run(0) | Out-Null +while($Scheduler.GetRunningTasks(0) | Where-Object {$_.Name -eq $TaskName}) +{ + Start-Sleep -s 1 +} + +#Clean up +$RootFolder.DeleteTask($TaskName,0) +Remove-Item $ScriptFile + +} +## [END] Install-WindowsUpdates ## +function Set-AutomaticUpdatesOptions { +<# + +.SYNOPSIS +Script that set windows update automatic update options in registry key. + +.DESCRIPTION +Script that set windows update automatic update options in registry key. + +.EXAMPLE +Set AUoptions +PS C:\> Set-AUoptions "2" + +.ROLE +Administrators + +#> + +Param( +[Parameter(Mandatory = $true)] +[string]$AUOptions +) + +$Path = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" +switch($AUOptions) +{ + '0' # Not defined, delete registry folder if exist + { + if (Test-Path $Path) { + Remove-Item $Path + } + } + '1' # Disabled, set NoAutoUpdate to 1 and delete AUOptions if existed + { + if (Test-Path $Path) { + Set-ItemProperty -Path $Path -Name NoAutoUpdate -Value 0x1 -Force + Remove-ItemProperty -Path $Path -Name AUOptions + } + else { + New-Item $Path -Force + New-ItemProperty -Path $Path -Name NoAutoUpdate -Value 0x1 -Force + } + } + default # else 2-5, set AUoptions + { + if (!(Test-Path $Path)) { + New-Item $Path -Force + } + Set-ItemProperty -Path $Path -Name AUOptions -Value $AUOptions -Force + Set-ItemProperty -Path $Path -Name NoAutoUpdate -Value 0x0 -Force + } +} + +} +## [END] Set-AutomaticUpdatesOptions ## +function Get-CimWin32ComputerSystem { +<# + +.SYNOPSIS +Gets Win32_ComputerSystem object. + +.DESCRIPTION +Gets Win32_ComputerSystem object. + +.ROLE +Readers + +#> +##SkipCheck=true## + + +import-module CimCmdlets + +Get-CimInstance -Namespace root/cimv2 -ClassName Win32_ComputerSystem + +} +## [END] Get-CimWin32ComputerSystem ## +function Get-CimWin32LogicalDisk { +<# + +.SYNOPSIS +Gets Win32_LogicalDisk object. + +.DESCRIPTION +Gets Win32_LogicalDisk object. + +.ROLE +Readers + +#> +##SkipCheck=true## + + +import-module CimCmdlets + +Get-CimInstance -Namespace root/cimv2 -ClassName Win32_LogicalDisk + +} +## [END] Get-CimWin32LogicalDisk ## +function Get-CimWin32NetworkAdapter { +<# + +.SYNOPSIS +Gets Win32_NetworkAdapter object. + +.DESCRIPTION +Gets Win32_NetworkAdapter object. + +.ROLE +Readers + +#> +##SkipCheck=true## + + +import-module CimCmdlets + +Get-CimInstance -Namespace root/cimv2 -ClassName Win32_NetworkAdapter + +} +## [END] Get-CimWin32NetworkAdapter ## +function Get-CimWin32OperatingSystem { +<# + +.SYNOPSIS +Gets Win32_OperatingSystem object. + +.DESCRIPTION +Gets Win32_OperatingSystem object. + +.ROLE +Readers + +#> +##SkipCheck=true## + + +import-module CimCmdlets + +Get-CimInstance -Namespace root/cimv2 -ClassName Win32_OperatingSystem + +} +## [END] Get-CimWin32OperatingSystem ## +function Get-CimWin32PhysicalMemory { +<# + +.SYNOPSIS +Gets Win32_PhysicalMemory object. + +.DESCRIPTION +Gets Win32_PhysicalMemory object. + +.ROLE +Readers + +#> +##SkipCheck=true## + + +import-module CimCmdlets + +Get-CimInstance -Namespace root/cimv2 -ClassName Win32_PhysicalMemory + +} +## [END] Get-CimWin32PhysicalMemory ## +function Get-CimWin32Processor { +<# + +.SYNOPSIS +Gets Win32_Processor object. + +.DESCRIPTION +Gets Win32_Processor object. + +.ROLE +Readers + +#> +##SkipCheck=true## + + +import-module CimCmdlets + +Get-CimInstance -Namespace root/cimv2 -ClassName Win32_Processor + +} +## [END] Get-CimWin32Processor ## +function Get-ClusterInventory { +<# + +.SYNOPSIS +Retrieves the inventory data for a cluster. + +.DESCRIPTION +Retrieves the inventory data for a cluster. + +.ROLE +Readers + +#> + +import-module CimCmdlets -ErrorAction SilentlyContinue + +# JEA code requires to pre-import the module (this is slow on failover cluster environment.) +import-module FailoverClusters -ErrorAction SilentlyContinue + +<# + +.SYNOPSIS +Get the name of this computer. + +.DESCRIPTION +Get the best available name for this computer. The FQDN is preferred, but when not avaialble +the NetBIOS name will be used instead. + +#> + +function getComputerName() { + $computerSystem = Get-CimInstance Win32_ComputerSystem -ErrorAction SilentlyContinue | Microsoft.PowerShell.Utility\Select-Object Name, DNSHostName + + if ($computerSystem) { + $computerName = $computerSystem.DNSHostName + + if ($null -eq $computerName) { + $computerName = $computerSystem.Name + } + + return $computerName + } + + return $null +} + +<# + +.SYNOPSIS +Are the cluster PowerShell cmdlets installed on this server? + +.DESCRIPTION +Are the cluster PowerShell cmdlets installed on this server? + +#> + +function getIsClusterCmdletAvailable() { + $cmdlet = Get-Command "Get-Cluster" -ErrorAction SilentlyContinue + + return !!$cmdlet +} + +<# + +.SYNOPSIS +Get the MSCluster Cluster CIM instance from this server. + +.DESCRIPTION +Get the MSCluster Cluster CIM instance from this server. + +#> +function getClusterCimInstance() { + $namespace = Get-CimInstance -Namespace root/MSCluster -ClassName __NAMESPACE -ErrorAction SilentlyContinue + + if ($namespace) { + return Get-CimInstance -Namespace root/mscluster MSCluster_Cluster -ErrorAction SilentlyContinue | Microsoft.PowerShell.Utility\Select-Object fqdn, S2DEnabled + } + + return $null +} + + +<# + +.SYNOPSIS +Determines if the current cluster supports Failover Clusters Time Series Database. + +.DESCRIPTION +Use the existance of the path value of cmdlet Get-StorageHealthSetting to determine if TSDB +is supported or not. + +#> +function getClusterPerformanceHistoryPath() { + return $null -ne (Get-StorageSubSystem clus* | Get-StorageHealthSetting -Name "System.PerformanceHistory.Path") +} + +<# + +.SYNOPSIS +Get some basic information about the cluster from the cluster. + +.DESCRIPTION +Get the needed cluster properties from the cluster. + +#> +function getClusterInfo() { + $returnValues = @{} + + $returnValues.Fqdn = $null + $returnValues.isS2DEnabled = $false + $returnValues.isTsdbEnabled = $false + + $cluster = getClusterCimInstance + if ($cluster) { + $returnValues.Fqdn = $cluster.fqdn + $isS2dEnabled = !!(Get-Member -InputObject $cluster -Name "S2DEnabled") -and ($cluster.S2DEnabled -eq 1) + $returnValues.isS2DEnabled = $isS2dEnabled + + if ($isS2DEnabled) { + $returnValues.isTsdbEnabled = getClusterPerformanceHistoryPath + } else { + $returnValues.isTsdbEnabled = $false + } + } + + return $returnValues +} + +<# + +.SYNOPSIS +Are the cluster PowerShell Health cmdlets installed on this server? + +.DESCRIPTION +Are the cluster PowerShell Health cmdlets installed on this server? + +s#> +function getisClusterHealthCmdletAvailable() { + $cmdlet = Get-Command -Name "Get-HealthFault" -ErrorAction SilentlyContinue + + return !!$cmdlet +} +<# + +.SYNOPSIS +Are the Britannica (sddc management resources) available on the cluster? + +.DESCRIPTION +Are the Britannica (sddc management resources) available on the cluster? + +#> +function getIsBritannicaEnabled() { + return $null -ne (Get-CimInstance -Namespace root/sddc/management -ClassName SDDC_Cluster -ErrorAction SilentlyContinue) +} + +<# + +.SYNOPSIS +Are the Britannica (sddc management resources) virtual machine available on the cluster? + +.DESCRIPTION +Are the Britannica (sddc management resources) virtual machine available on the cluster? + +#> +function getIsBritannicaVirtualMachineEnabled() { + return $null -ne (Get-CimInstance -Namespace root/sddc/management -ClassName SDDC_VirtualMachine -ErrorAction SilentlyContinue) +} + +<# + +.SYNOPSIS +Are the Britannica (sddc management resources) virtual switch available on the cluster? + +.DESCRIPTION +Are the Britannica (sddc management resources) virtual switch available on the cluster? + +#> +function getIsBritannicaVirtualSwitchEnabled() { + return $null -ne (Get-CimInstance -Namespace root/sddc/management -ClassName SDDC_VirtualSwitch -ErrorAction SilentlyContinue) +} + +########################################################################### +# main() +########################################################################### + +$clusterInfo = getClusterInfo + +$result = New-Object PSObject + +$result | Add-Member -MemberType NoteProperty -Name 'Fqdn' -Value $clusterInfo.Fqdn +$result | Add-Member -MemberType NoteProperty -Name 'IsS2DEnabled' -Value $clusterInfo.isS2DEnabled +$result | Add-Member -MemberType NoteProperty -Name 'IsTsdbEnabled' -Value $clusterInfo.isTsdbEnabled +$result | Add-Member -MemberType NoteProperty -Name 'IsClusterHealthCmdletAvailable' -Value (getIsClusterHealthCmdletAvailable) +$result | Add-Member -MemberType NoteProperty -Name 'IsBritannicaEnabled' -Value (getIsBritannicaEnabled) +$result | Add-Member -MemberType NoteProperty -Name 'IsBritannicaVirtualMachineEnabled' -Value (getIsBritannicaVirtualMachineEnabled) +$result | Add-Member -MemberType NoteProperty -Name 'IsBritannicaVirtualSwitchEnabled' -Value (getIsBritannicaVirtualSwitchEnabled) +$result | Add-Member -MemberType NoteProperty -Name 'IsClusterCmdletAvailable' -Value (getIsClusterCmdletAvailable) +$result | Add-Member -MemberType NoteProperty -Name 'CurrentClusterNode' -Value (getComputerName) + +$result + +} +## [END] Get-ClusterInventory ## +function Get-ClusterNodes { +<# + +.SYNOPSIS +Retrieves the inventory data for cluster nodes in a particular cluster. + +.DESCRIPTION +Retrieves the inventory data for cluster nodes in a particular cluster. + +.ROLE +Readers + +#> + +import-module CimCmdlets + +# JEA code requires to pre-import the module (this is slow on failover cluster environment.) +import-module FailoverClusters -ErrorAction SilentlyContinue + +############################################################################### +# Constants +############################################################################### + +Set-Variable -Name LogName -Option Constant -Value "Microsoft-ServerManagementExperience" -ErrorAction SilentlyContinue +Set-Variable -Name LogSource -Option Constant -Value "SMEScripts" -ErrorAction SilentlyContinue +Set-Variable -Name ScriptName -Option Constant -Value $MyInvocation.ScriptName -ErrorAction SilentlyContinue + +<# + +.SYNOPSIS +Are the cluster PowerShell cmdlets installed? + +.DESCRIPTION +Use the Get-Command cmdlet to quickly test if the cluster PowerShell cmdlets +are installed on this server. + +#> + +function getClusterPowerShellSupport() { + $cmdletInfo = Get-Command 'Get-ClusterNode' -ErrorAction SilentlyContinue + + return $cmdletInfo -and $cmdletInfo.Name -eq "Get-ClusterNode" +} + +<# + +.SYNOPSIS +Get the cluster nodes using the cluster CIM provider. + +.DESCRIPTION +When the cluster PowerShell cmdlets are not available fallback to using +the cluster CIM provider to get the needed information. + +#> + +function getClusterNodeCimInstances() { + # Change the WMI property NodeDrainStatus to DrainStatus to match the PS cmdlet output. + return Get-CimInstance -Namespace root/mscluster MSCluster_Node -ErrorAction SilentlyContinue | ` + Microsoft.PowerShell.Utility\Select-Object @{Name="DrainStatus"; Expression={$_.NodeDrainStatus}}, DynamicWeight, Name, NodeWeight, FaultDomain, State +} + +<# + +.SYNOPSIS +Get the cluster nodes using the cluster PowerShell cmdlets. + +.DESCRIPTION +When the cluster PowerShell cmdlets are available use this preferred function. + +#> + +function getClusterNodePsInstances() { + return Get-ClusterNode -ErrorAction SilentlyContinue | Microsoft.PowerShell.Utility\Select-Object DrainStatus, DynamicWeight, Name, NodeWeight, FaultDomain, State +} + +<# + +.SYNOPSIS +Use DNS services to get the FQDN of the cluster NetBIOS name. + +.DESCRIPTION +Use DNS services to get the FQDN of the cluster NetBIOS name. + +.Notes +It is encouraged that the caller add their approprate -ErrorAction when +calling this function. + +#> + +function getClusterNodeFqdn([string]$clusterNodeName) { + return ([System.Net.Dns]::GetHostEntry($clusterNodeName)).HostName +} + +<# + +.SYNOPSIS +Writes message to event log as warning. + +.DESCRIPTION +Writes message to event log as warning. + +#> + +function writeToEventLog([string]$message) { + Microsoft.PowerShell.Management\New-EventLog -LogName $LogName -Source $LogSource -ErrorAction SilentlyContinue + Microsoft.PowerShell.Management\Write-EventLog -LogName $LogName -Source $LogSource -EventId 0 -Category 0 -EntryType Warning ` + -Message $message -ErrorAction SilentlyContinue +} + +<# + +.SYNOPSIS +Get the cluster nodes. + +.DESCRIPTION +When the cluster PowerShell cmdlets are available get the information about the cluster nodes +using PowerShell. When the cmdlets are not available use the Cluster CIM provider. + +#> + +function getClusterNodes() { + $isClusterCmdletAvailable = getClusterPowerShellSupport + + if ($isClusterCmdletAvailable) { + $clusterNodes = getClusterNodePsInstances + } else { + $clusterNodes = getClusterNodeCimInstances + } + + $clusterNodeMap = @{} + + foreach ($clusterNode in $clusterNodes) { + $clusterNodeName = $clusterNode.Name.ToLower() + try + { + $clusterNodeFqdn = getClusterNodeFqdn $clusterNodeName -ErrorAction SilentlyContinue + } + catch + { + $clusterNodeFqdn = $clusterNodeName + writeToEventLog "[$ScriptName]: The fqdn for node '$clusterNodeName' could not be obtained. Defaulting to machine name '$clusterNodeName'" + } + + $clusterNodeResult = New-Object PSObject + + $clusterNodeResult | Add-Member -MemberType NoteProperty -Name 'FullyQualifiedDomainName' -Value $clusterNodeFqdn + $clusterNodeResult | Add-Member -MemberType NoteProperty -Name 'Name' -Value $clusterNodeName + $clusterNodeResult | Add-Member -MemberType NoteProperty -Name 'DynamicWeight' -Value $clusterNode.DynamicWeight + $clusterNodeResult | Add-Member -MemberType NoteProperty -Name 'NodeWeight' -Value $clusterNode.NodeWeight + $clusterNodeResult | Add-Member -MemberType NoteProperty -Name 'FaultDomain' -Value $clusterNode.FaultDomain + $clusterNodeResult | Add-Member -MemberType NoteProperty -Name 'State' -Value $clusterNode.State + $clusterNodeResult | Add-Member -MemberType NoteProperty -Name 'DrainStatus' -Value $clusterNode.DrainStatus + + $clusterNodeMap.Add($clusterNodeName, $clusterNodeResult) + } + + return $clusterNodeMap +} + +########################################################################### +# main() +########################################################################### + +getClusterNodes + +} +## [END] Get-ClusterNodes ## +function Get-ServerInventory { +<# + +.SYNOPSIS +Retrieves the inventory data for a server. + +.DESCRIPTION +Retrieves the inventory data for a server. + +.ROLE +Readers + +#> + +Set-StrictMode -Version 5.0 + +Import-Module CimCmdlets + +<# + +.SYNOPSIS +Converts an arbitrary version string into just 'Major.Minor' + +.DESCRIPTION +To make OS version comparisons we only want to compare the major and +minor version. Build number and/os CSD are not interesting. + +#> + +function convertOsVersion([string]$osVersion) { + [Ref]$parsedVersion = $null + if (![Version]::TryParse($osVersion, $parsedVersion)) { + return $null + } + + $version = [Version]$parsedVersion.Value + return New-Object Version -ArgumentList $version.Major, $version.Minor +} + +<# + +.SYNOPSIS +Determines if CredSSP is enabled for the current server or client. + +.DESCRIPTION +Check the registry value for the CredSSP enabled state. + +#> + +function isCredSSPEnabled() { + Set-Variable credSSPServicePath -Option Constant -Value "WSMan:\localhost\Service\Auth\CredSSP" + Set-Variable credSSPClientPath -Option Constant -Value "WSMan:\localhost\Client\Auth\CredSSP" + + $credSSPServerEnabled = $false; + $credSSPClientEnabled = $false; + + $credSSPServerService = Get-Item $credSSPServicePath -ErrorAction SilentlyContinue + if ($credSSPServerService) { + $credSSPServerEnabled = [System.Convert]::ToBoolean($credSSPServerService.Value) + } + + $credSSPClientService = Get-Item $credSSPClientPath -ErrorAction SilentlyContinue + if ($credSSPClientService) { + $credSSPClientEnabled = [System.Convert]::ToBoolean($credSSPClientService.Value) + } + + return ($credSSPServerEnabled -or $credSSPClientEnabled) +} + +<# + +.SYNOPSIS +Determines if the Hyper-V role is installed for the current server or client. + +.DESCRIPTION +The Hyper-V role is installed when the VMMS service is available. This is much +faster then checking Get-WindowsFeature and works on Windows Client SKUs. + +#> + +function isHyperVRoleInstalled() { + $vmmsService = Get-Service -Name "VMMS" -ErrorAction SilentlyContinue + + return $vmmsService -and $vmmsService.Name -eq "VMMS" +} + +<# + +.SYNOPSIS +Determines if the Hyper-V PowerShell support module is installed for the current server or client. + +.DESCRIPTION +The Hyper-V PowerShell support module is installed when the modules cmdlets are available. This is much +faster then checking Get-WindowsFeature and works on Windows Client SKUs. + +#> +function isHyperVPowerShellSupportInstalled() { + # quicker way to find the module existence. it doesn't load the module. + return !!(Get-Module -ListAvailable Hyper-V -ErrorAction SilentlyContinue) +} + +<# + +.SYNOPSIS +Determines if Windows Management Framework (WMF) 5.0, or higher, is installed for the current server or client. + +.DESCRIPTION +Windows Admin Center requires WMF 5 so check the registey for WMF version on Windows versions that are less than +Windows Server 2016. + +#> +function isWMF5Installed([string] $operatingSystemVersion) { + Set-Variable Server2016 -Option Constant -Value (New-Object Version '10.0') # And Windows 10 client SKUs + Set-Variable Server2012 -Option Constant -Value (New-Object Version '6.2') + + $version = convertOsVersion $operatingSystemVersion + if (-not $version) { + # Since the OS version string is not properly formatted we cannot know the true installed state. + return $false + } + + if ($version -ge $Server2016) { + # It's okay to assume that 2016 and up comes with WMF 5 or higher installed + return $true + } + else { + if ($version -ge $Server2012) { + # Windows 2012/2012R2 are supported as long as WMF 5 or higher is installed + $registryKey = 'HKLM:\SOFTWARE\Microsoft\PowerShell\3\PowerShellEngine' + $registryKeyValue = Get-ItemProperty -Path $registryKey -Name PowerShellVersion -ErrorAction SilentlyContinue + + if ($registryKeyValue -and ($registryKeyValue.PowerShellVersion.Length -ne 0)) { + $installedWmfVersion = [Version]$registryKeyValue.PowerShellVersion + + if ($installedWmfVersion -ge [Version]'5.0') { + return $true + } + } + } + } + + return $false +} + +<# + +.SYNOPSIS +Determines if the current usser is a system administrator of the current server or client. + +.DESCRIPTION +Determines if the current usser is a system administrator of the current server or client. + +#> +function isUserAnAdministrator() { + return ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator") +} + +<# + +.SYNOPSIS +Get some basic information about the Failover Cluster that is running on this server. + +.DESCRIPTION +Create a basic inventory of the Failover Cluster that may be running in this server. + +#> +function getClusterInformation() { + $returnValues = @{} + + $returnValues.IsS2dEnabled = $false + $returnValues.IsCluster = $false + $returnValues.ClusterFqdn = $null + + $namespace = Get-CimInstance -Namespace root/MSCluster -ClassName __NAMESPACE -ErrorAction SilentlyContinue + if ($namespace) { + $cluster = Get-CimInstance -Namespace root/MSCluster -ClassName MSCluster_Cluster -ErrorAction SilentlyContinue + if ($cluster) { + $returnValues.IsCluster = $true + $returnValues.ClusterFqdn = $cluster.Fqdn + $returnValues.IsS2dEnabled = !!(Get-Member -InputObject $cluster -Name "S2DEnabled") -and ($cluster.S2DEnabled -gt 0) + } + } + + return $returnValues +} + +<# + +.SYNOPSIS +Get the Fully Qaulified Domain (DNS domain) Name (FQDN) of the passed in computer name. + +.DESCRIPTION +Get the Fully Qaulified Domain (DNS domain) Name (FQDN) of the passed in computer name. + +#> +function getComputerFqdnAndAddress($computerName) { + $hostEntry = [System.Net.Dns]::GetHostEntry($computerName) + $addressList = @() + foreach ($item in $hostEntry.AddressList) { + $address = New-Object PSObject + $address | Add-Member -MemberType NoteProperty -Name 'IpAddress' -Value $item.ToString() + $address | Add-Member -MemberType NoteProperty -Name 'AddressFamily' -Value $item.AddressFamily.ToString() + $addressList += $address + } + + $result = New-Object PSObject + $result | Add-Member -MemberType NoteProperty -Name 'Fqdn' -Value $hostEntry.HostName + $result | Add-Member -MemberType NoteProperty -Name 'AddressList' -Value $addressList + return $result +} + +<# + +.SYNOPSIS +Get the Fully Qaulified Domain (DNS domain) Name (FQDN) of the current server or client. + +.DESCRIPTION +Get the Fully Qaulified Domain (DNS domain) Name (FQDN) of the current server or client. + +#> +function getHostFqdnAndAddress($computerSystem) { + $computerName = $computerSystem.DNSHostName + if (!$computerName) { + $computerName = $computerSystem.Name + } + + return getComputerFqdnAndAddress $computerName +} + +<# + +.SYNOPSIS +Are the needed management CIM interfaces available on the current server or client. + +.DESCRIPTION +Check for the presence of the required server management CIM interfaces. + +#> +function getManagementToolsSupportInformation() { + $returnValues = @{} + + $returnValues.ManagementToolsAvailable = $false + $returnValues.ServerManagerAvailable = $false + + $namespaces = Get-CimInstance -Namespace root/microsoft/windows -ClassName __NAMESPACE -ErrorAction SilentlyContinue + + if ($namespaces) { + $returnValues.ManagementToolsAvailable = !!($namespaces | Where-Object { $_.Name -ieq "ManagementTools" }) + $returnValues.ServerManagerAvailable = !!($namespaces | Where-Object { $_.Name -ieq "ServerManager" }) + } + + return $returnValues +} + +<# + +.SYNOPSIS +Check the remote app enabled or not. + +.DESCRIPTION +Check the remote app enabled or not. + +#> +function isRemoteAppEnabled() { + Set-Variable key -Option Constant -Value "HKLM:\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Terminal Server\\TSAppAllowList" + + $registryKeyValue = Get-ItemProperty -Path $key -Name fDisabledAllowList -ErrorAction SilentlyContinue + + if (-not $registryKeyValue) { + return $false + } + return $registryKeyValue.fDisabledAllowList -eq 1 +} + +<# + +.SYNOPSIS +Check the remote app enabled or not. + +.DESCRIPTION +Check the remote app enabled or not. + +#> + +<# +c +.SYNOPSIS +Get the Win32_OperatingSystem information + +.DESCRIPTION +Get the Win32_OperatingSystem instance and filter the results to just the required properties. +This filtering will make the response payload much smaller. + +#> +function getOperatingSystemInfo() { + return Get-CimInstance Win32_OperatingSystem | Microsoft.PowerShell.Utility\Select-Object csName, Caption, OperatingSystemSKU, Version, ProductType +} + +<# + +.SYNOPSIS +Get the Win32_ComputerSystem information + +.DESCRIPTION +Get the Win32_ComputerSystem instance and filter the results to just the required properties. +This filtering will make the response payload much smaller. + +#> +function getComputerSystemInfo() { + return Get-CimInstance Win32_ComputerSystem -ErrorAction SilentlyContinue | ` + Microsoft.PowerShell.Utility\Select-Object TotalPhysicalMemory, DomainRole, Manufacturer, Model, NumberOfLogicalProcessors, Domain, Workgroup, DNSHostName, Name, PartOfDomain +} + +########################################################################### +# main() +########################################################################### + +$operatingSystem = getOperatingSystemInfo +$computerSystem = getComputerSystemInfo +$isAdministrator = isUserAnAdministrator +$fqdnAndAddress = getHostFqdnAndAddress $computerSystem +$hostname = hostname +$netbios = $env:ComputerName +$managementToolsInformation = getManagementToolsSupportInformation +$isWmfInstalled = isWMF5Installed $operatingSystem.Version +$clusterInformation = getClusterInformation -ErrorAction SilentlyContinue +$isHyperVPowershellInstalled = isHyperVPowerShellSupportInstalled +$isHyperVRoleInstalled = isHyperVRoleInstalled +$isCredSSPEnabled = isCredSSPEnabled +$isRemoteAppEnabled = isRemoteAppEnabled + +$result = New-Object PSObject +$result | Add-Member -MemberType NoteProperty -Name 'IsAdministrator' -Value $isAdministrator +$result | Add-Member -MemberType NoteProperty -Name 'OperatingSystem' -Value $operatingSystem +$result | Add-Member -MemberType NoteProperty -Name 'ComputerSystem' -Value $computerSystem +$result | Add-Member -MemberType NoteProperty -Name 'Fqdn' -Value $fqdnAndAddress.Fqdn +$result | Add-Member -MemberType NoteProperty -Name 'AddressList' -Value $fqdnAndAddress.AddressList +$result | Add-Member -MemberType NoteProperty -Name 'Hostname' -Value $hostname +$result | Add-Member -MemberType NoteProperty -Name 'NetBios' -Value $netbios +$result | Add-Member -MemberType NoteProperty -Name 'IsManagementToolsAvailable' -Value $managementToolsInformation.ManagementToolsAvailable +$result | Add-Member -MemberType NoteProperty -Name 'IsServerManagerAvailable' -Value $managementToolsInformation.ServerManagerAvailable +$result | Add-Member -MemberType NoteProperty -Name 'IsWmfInstalled' -Value $isWmfInstalled +$result | Add-Member -MemberType NoteProperty -Name 'IsCluster' -Value $clusterInformation.IsCluster +$result | Add-Member -MemberType NoteProperty -Name 'ClusterFqdn' -Value $clusterInformation.ClusterFqdn +$result | Add-Member -MemberType NoteProperty -Name 'IsS2dEnabled' -Value $clusterInformation.IsS2dEnabled +$result | Add-Member -MemberType NoteProperty -Name 'IsHyperVRoleInstalled' -Value $isHyperVRoleInstalled +$result | Add-Member -MemberType NoteProperty -Name 'IsHyperVPowershellInstalled' -Value $isHyperVPowershellInstalled +$result | Add-Member -MemberType NoteProperty -Name 'IsCredSSPEnabled' -Value $isCredSSPEnabled +$result | Add-Member -MemberType NoteProperty -Name 'IsRemoteAppEnabled' -Value $isRemoteAppEnabled + +$result + +} +## [END] Get-ServerInventory ## + diff --git a/Scripts/PSDWizard.psm1 b/Scripts/PSDWizard.psm1 index 8421175..f6abaff 100644 --- a/Scripts/PSDWizard.psm1 +++ b/Scripts/PSDWizard.psm1 @@ -2,7 +2,7 @@ # PSDWizard.psm1 # -$verbosePreference = "Continue" +#$verbosePreference = "Continue" $script:Wizard = $null $script:Xaml = $null @@ -22,7 +22,7 @@ function Get-PSDWizard # Store objects in PowerShell variables $script:Xaml.SelectNodes("//*[@Name]") | % { - Write-Verbose "Creating variable $($_.Name)" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Creating variable $($_.Name)" Set-Variable -Name ($_.Name) -Value $script:Wizard.FindName($_.Name) -Scope Global } @@ -52,7 +52,7 @@ function Save-PSDWizardResult $control = $script:Wizard.FindName($_.Name) $value = $control.Text Set-Item -Path tsenv:$name -Value $value - Write-Verbose "Set variable $name using form value $value" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Set variable $name using form value $value" } } @@ -72,11 +72,12 @@ function Show-PSDWizard $xamlPath ) - Write-Verbose "Processing wizard from $xamlPath" + Write-PSDLog -Message "$($MyInvocation.MyCommand.Name): Processing wizard from $xamlPath" $wizard = Get-PSDWizard $xamlPath Set-PSDWizardDefault $result = $wizard.ShowDialog() Save-PSDWizardResult + Return $wizard } Export-ModuleMember -function Show-PSDWizard diff --git a/Scripts/PSDWizard.xaml b/Scripts/PSDWizard.xaml index b5d555b..22d8f65 100644 --- a/Scripts/PSDWizard.xaml +++ b/Scripts/PSDWizard.xaml @@ -18,11 +18,8 @@ -