diff --git a/Functions/Copy-ItemWithProgress.ps1 b/Functions/Copy-ItemWithProgress.ps1 new file mode 100644 index 0000000..1cbb7fa --- /dev/null +++ b/Functions/Copy-ItemWithProgress.ps1 @@ -0,0 +1,621 @@ +## Microsoft Function Naming Convention: http://msdn.microsoft.com/en-us/library/ms714428(v=vs.85).aspx + +#region Function Copy-ItemWithProgress +Function Copy-ItemWithProgress + { + <# + .SYNOPSIS + Copies file(s) from a valid path in segments. + + .DESCRIPTION + Supports the copying of files from a valid location in segments either directly or recursively. + Supports the usage of filters and exclusions exactly the way that the Get-ChildItem or Copy-Item cmdlets work. + + .PARAMETER Path + A valid file or folder location. + + .PARAMETER Destination + The destination directory where the content will be copied. + + .PARAMETER Include + One or more filter(s) to implicitly copy what is specified. + + .PARAMETER Exclude + One or more filter(s) to implicitly skip the copying of what is specified. + + .PARAMETER Recurse + Recurse through the specified directories. + + .PARAMETER Force + Overwrite file(s) that have already been copied. The default behavior is to skip the copying of the file. + + .PARAMETER SegmentSize + The segment size in megabytes that each file will be transferred with. + + .PARAMETER RandomDelay + Adds a pulse in between the transfer of each segment. + + .PARAMETER ContinueOnError + Continues processing even if an error has occured. + + .EXAMPLE + Copy-ItemWithProgress -Path 'FileOrDirectoryPath' -Destination 'YourDestinationDirectory' -Verbose + + .EXAMPLE + $CopyItemWithProgressParameters = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary' + $CopyItemWithProgressParameters.Path = "FileOrDirectoryPath" + $CopyItemWithProgressParameters.Destination = "YourDestinationDirectory" + $CopyItemWithProgressParameters.Include = New-Object -TypeName 'System.Collections.Generic.List[String]' + $CopyItemWithProgressParameters.Include.Add('*.*') + $CopyItemWithProgressParameters.Exclude = New-Object -TypeName 'System.Collections.Generic.List[String]' + $CopyItemWithProgressParameters.Exclude.Add('*.ini') + $CopyItemWithProgressParameters.Recurse = $True + $CopyItemWithProgressParameters.Force = $False + $CopyItemWithProgressParameters.SegmentSize = 4096 + $CopyItemWithProgressParameters.RandomDelay = $False + $CopyItemWithProgressParameters.ContinueOnError = $False + $CopyItemWithProgressParameters.Verbose = $True + + $CopyItemWithProgressResult = Copy-ItemWithProgress @CopyItemWithProgressParameters + + Write-Output -InputObject ($CopyItemWithProgressResult) + + .NOTES + A progress bar is displayed and can be useful for scripts where the copying of large file(s) is taking place. + + .LINK + http://stackoverflow.com/questions/2434133/progress-during-large-file-copy-copy-item-write-progress + + .LINK + https://stackoverflow.com/questions/13883404/custom-robocopy-progress-bar-in-powershell + #> + + [CmdletBinding()] + + Param + ( + [Parameter(Mandatory=$True)] + [ValidateNotNullOrEmpty()] + [ValidateScript({Test-Path -Path $_})] + [Alias('P')] + [String]$Path, + + [Parameter(Mandatory=$True)] + [ValidateNotNullOrEmpty()] + [Alias('D')] + [System.IO.DirectoryInfo]$Destination, + + [Parameter(Mandatory=$False)] + [Alias('I')] + [String[]]$Include, + + [Parameter(Mandatory=$False)] + [Alias('E')] + [String[]]$Exclude, + + [Parameter(Mandatory=$False)] + [Alias('R')] + [Switch]$Recurse, + + [Parameter(Mandatory=$False)] + [Alias('Overwrite')] + [Switch]$Force, + + [Parameter(Mandatory=$False)] + [ValidateNotNullOrEmpty()] + [Alias('SS', 'BufferInMegabytes')] + [Int]$SegmentSize, + + [Parameter(Mandatory=$False)] + [Alias('RD')] + [Switch]$RandomDelay, + + [Parameter(Mandatory=$False)] + [Alias('COE')] + [Switch]$ContinueOnError + ) + + Begin + { + + + Try + { + $DateTimeLogFormat = 'dddd, MMMM dd, yyyy @ hh:mm:ss.FFF tt' ###Monday, January 01, 2019 @ 10:15:34.000 AM### + [ScriptBlock]$GetCurrentDateTimeLogFormat = {(Get-Date).ToString($DateTimeLogFormat)} + $DateTimeMessageFormat = 'MM/dd/yyyy HH:mm:ss.FFF' ###03/23/2022 11:12:48.347### + [ScriptBlock]$GetCurrentDateTimeMessageFormat = {(Get-Date).ToString($DateTimeMessageFormat)} + $DateFileFormat = 'yyyyMMdd' ###20190403### + [ScriptBlock]$GetCurrentDateFileFormat = {(Get-Date).ToString($DateFileFormat)} + $DateTimeFileFormat = 'yyyyMMdd_HHmmss' ###20190403_115354### + [ScriptBlock]$GetCurrentDateTimeFileFormat = {(Get-Date).ToString($DateTimeFileFormat)} + $TextInfo = (Get-Culture).TextInfo + $LoggingDetails = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary' + $LoggingDetails.Add('LogMessage', $Null) + $LoggingDetails.Add('WarningMessage', $Null) + $LoggingDetails.Add('ErrorMessage', $Null) + $CommonParameterList = New-Object -TypeName 'System.Collections.Generic.List[String]' + $CommonParameterList.AddRange([System.Management.Automation.PSCmdlet]::CommonParameters) + $CommonParameterList.AddRange([System.Management.Automation.PSCmdlet]::OptionalCommonParameters) + $FileSystemObject = New-Object -ComObject 'Scripting.FileSystemObject' + + [ScriptBlock]$ErrorHandlingDefinition = { + $ErrorMessageList = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary' + $ErrorMessageList.Add('Message', $_.Exception.Message) + $ErrorMessageList.Add('Category', $_.Exception.ErrorRecord.FullyQualifiedErrorID) + $ErrorMessageList.Add('Script', $_.InvocationInfo.ScriptName) + $ErrorMessageList.Add('LineNumber', $_.InvocationInfo.ScriptLineNumber) + $ErrorMessageList.Add('LinePosition', $_.InvocationInfo.OffsetInLine) + $ErrorMessageList.Add('Code', $_.InvocationInfo.Line.Trim()) + + ForEach ($ErrorMessage In $ErrorMessageList.GetEnumerator()) + { + $LoggingDetails.ErrorMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - ERROR: $($ErrorMessage.Key): $($ErrorMessage.Value)" + Write-Warning -Message ($LoggingDetails.ErrorMessage) + } + + Switch (($ContinueOnError.IsPresent -eq $False) -or ($ContinueOnError -eq $False)) + { + {($_ -eq $True)} + { + Throw + } + } + } + + #Determine the date and time we executed the function + $FunctionStartTime = (Get-Date) + + [String]$FunctionName = $MyInvocation.MyCommand + [System.IO.FileInfo]$InvokingScriptPath = $MyInvocation.PSCommandPath + [System.IO.DirectoryInfo]$InvokingScriptDirectory = $InvokingScriptPath.Directory.FullName + [System.IO.FileInfo]$FunctionPath = "$($InvokingScriptDirectory.FullName)\Functions\$($FunctionName).ps1" + [System.IO.DirectoryInfo]$FunctionDirectory = "$($FunctionPath.Directory.FullName)" + + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Function `'$($FunctionName)`' is beginning. Please Wait..." + Write-Verbose -Message ($LoggingDetails.LogMessage) -Verbose + + #Define Default Action Preferences + $ErrorActionPreference = 'Stop' + + [String[]]$AvailableScriptParameters = (Get-Command -Name ($FunctionName)).Parameters.GetEnumerator() | Where-Object {($_.Value.Name -inotin $CommonParameterList)} | ForEach-Object {"-$($_.Value.Name):$($_.Value.ParameterType.Name)"} + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Available Function Parameter(s) = $($AvailableScriptParameters -Join ', ')" + Write-Verbose -Message ($LoggingDetails.LogMessage) -Verbose + + [String[]]$SuppliedScriptParameters = $PSBoundParameters.GetEnumerator() | ForEach-Object {"-$($_.Key):$($_.Value.GetType().Name)"} + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Supplied Function Parameter(s) = $($SuppliedScriptParameters -Join ', ')" + Write-Verbose -Message ($LoggingDetails.LogMessage) -Verbose + + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Execution of $($FunctionName) began on $($FunctionStartTime.ToString($DateTimeLogFormat))" + Write-Verbose -Message ($LoggingDetails.LogMessage) -Verbose + + #region Load any required libraries + [System.IO.DirectoryInfo]$LibariesDirectory = "$($FunctionDirectory.FullName)\Libraries" + + Switch ([System.IO.Directory]::Exists($LibariesDirectory.FullName)) + { + {($_ -eq $True)} + { + $LibraryPatternList = New-Object -TypeName 'System.Collections.Generic.List[String]' + #$LibraryPatternList.Add('') + + Switch ($LibraryPatternList.Count -gt 0) + { + {($_ -eq $True)} + { + $LibraryList = Get-ChildItem -Path ($LibariesDirectory.FullName) -Include ($LibraryPatternList.ToArray()) -Recurse -Force | Where-Object {($_ -is [System.IO.FileInfo])} + + $LibraryListCount = ($LibraryList | Measure-Object).Count + + Switch ($LibraryListCount -gt 0) + { + {($_ -eq $True)} + { + For ($LibraryListIndex = 0; $LibraryListIndex -lt $LibraryListCount; $LibraryListIndex++) + { + $Library = $LibraryList[$LibraryListIndex] + + [Byte[]]$LibraryBytes = [System.IO.File]::ReadAllBytes($Library.FullName) + + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Attempting to load assembly `"$($Library.FullName)`". Please Wait..." + Write-Verbose -Message ($LoggingDetails.LogMessage) + + $Null = [System.Reflection.Assembly]::Load($LibraryBytes) + } + } + } + } + } + } + } + #endregion + + #region Set Default Parameter Values + Switch ($True) + { + {($Null -ieq $SegmentSize) -or ($SegmentSize -eq 0)} + { + [Int]$SegmentSize = 8192 + } + } + #endregion + + #Create an object that will contain the functions output. + $OutputObjectList = New-Object -TypeName 'System.Collections.Generic.List[PSObject]' + } + Catch + { + $ErrorHandlingDefinition.Invoke() + } + Finally + { + + } + } + + Process + { + Try + { + Switch ($Null -ine $Path) + { + {($_ -eq $True)} + { + $GetChildItemParameters = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary' + $GetChildItemParameters.Path = $Path + $GetChildItemParameters.Force = $True + $GetChildItemParameters.ErrorAction = [System.Management.Automation.Actionpreference]::SilentlyContinue + + Switch ($True) + { + {([String]::IsNullOrEmpty($Include) -eq $False) -and ([String]::IsNullOrWhiteSpace($Include) -eq $False)} + { + $GetChildItemParameters.Include = $Include + + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - File(s) matching $($GetChildItemParameters.Include -Join ', ') will be included." + Write-Verbose -Message ($LoggingDetails.LogMessage) + } + + {([String]::IsNullOrEmpty($Exclude) -eq $False) -and ([String]::IsNullOrWhiteSpace($Exclude) -eq $False)} + { + $GetChildItemParameters.Exclude = $Exclude + + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - File(s) matching $($GetChildItemParameters.Exclude -Join ', ') will be excluded." + Write-Verbose -Message ($LoggingDetails.LogMessage) + } + + {($Recurse.IsPresent -eq $True)} + { + $GetChildItemParameters.Recurse = $True + + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - The search will be performed recursively." + Write-Verbose -Message ($LoggingDetails.LogMessage) + } + } + + $FileList = Get-ChildItem @GetChildItemParameters | Where-Object {($_ -is [System.IO.FileInfo])} + + $FileListDetails = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary' + $FileListDetails.Count = ($FileList | Measure-Object).Count + + Switch ($FileListDetails.Count -gt 0) + { + {($_ -eq $True)} + { + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - $($FileListDetails.Count) file(s) will be copied in $($SegmentSize) MB segments to the destination directory of `"$($Destination.FullName)`". Please Wait..." + Write-Verbose -Message ($LoggingDetails.LogMessage) -Verbose + + $FileListDetails.TotalBytes = ($FileList | Measure-Object -Sum 'Length').Sum + $FileListDetails.TransferredBytes = 0 + $FileListDetails.PercentComplete = 0 + + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - A total of $([System.Math]::Round(($FileListDetails.TotalBytes / 1MB), 2)) MB needs to be transferred." + Write-Verbose -Message ($LoggingDetails.LogMessage) -Verbose + + $StopWatchTable = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary' + $StopWatchTable.Primary = New-Object -TypeName 'System.Diagnostics.Stopwatch' + $StopWatchTable.Secondary = New-Object -TypeName 'System.Diagnostics.Stopwatch' + + $Null = $StopWatchTable.Primary.Start() + + For ($FileListIndex = 0; $FileListIndex -lt $FileListDetails.Count; $FileListIndex++) + { + Try + { + $FileObjectProperties = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary' + $FileObjectProperties.Source = ($FileList[$FileListIndex]) -As [System.IO.FileInfo] + + Switch ($Recurse.IsPresent) + { + {($_ -eq $True)} + { + $FileObjectProperties.SourceSegmentList = $FileObjectProperties.Source.FullName.Replace($Path, '').Split('\', [System.StringSplitOptions]::RemoveEmptyEntries) -As [System.Collections.Generic.List[String]] + + $FileObjectProperties.Destination = (Join-Path -Path ($Destination.FullName) -ChildPath ($FileObjectProperties.SourceSegmentList -Join '\')) -As [System.IO.FileInfo] + + $FileObjectProperties.Remove('SourceSegmentList') + } + + Default + { + $FileObjectProperties.Destination = "$($Destination.FullName)\$($FileObjectProperties.Source.Name)" -As [System.IO.FileInfo] + } + } + + Switch ($True) + { + {([System.IO.Directory]::Exists($FileObjectProperties.Destination.Directory.FullName) -eq $False)} + { + $Null = [System.IO.Directory]::CreateDirectory($FileObjectProperties.Destination.Directory.FullName) + } + } + + Switch (([System.IO.File]::Exists($FileObjectProperties.Destination.FullName) -eq $False) -or ($Force.IsPresent -eq $True)) + { + {($_ -eq $True)} + { + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Attempting to copy `"$($FileObjectProperties.Source.FullName)`" to `"$($FileObjectProperties.Destination.FullName)`". Please Wait..." + Write-Verbose -Message ($LoggingDetails.LogMessage) + + $FileDetails = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary' + $FileDetails.Source = [System.IO.File]::OpenRead($FileObjectProperties.Source.FullName) + $FileDetails.Destination = [System.IO.File]::Create($FileObjectProperties.Destination.FullName) + $FileDetails.Buffer = New-Object -TypeName 'Byte[]' -ArgumentList ($SegmentSize * 1024) + $FileDetails.SegmentBytes = 0 + $FileDetails.TransferredBytes = 0 + $FileDetails.Number = $FileListIndex + 1 + + $Null = $StopWatchTable.Secondary.Start() + + Do + { + $FileDetails.SegmentBytes = $FileDetails.Source.Read($FileDetails.Buffer, 0, $FileDetails.Buffer.Length) + + $FileDetails.Destination.Write($FileDetails.Buffer, 0, $FileDetails.SegmentBytes) + + $FileDetails.TransferredBytes = $FileDetails.TransferredBytes + $FileDetails.SegmentBytes + + $FileListDetails.TransferredBytes = $FileListDetails.TransferredBytes + $FileDetails.SegmentBytes + + Switch ($FileDetails.Source.Length -gt 1) + { + {($_ -eq $True)} + { + $FileDetails.PercentComplete = (($FileDetails.TransferredBytes / $FileDetails.Source.Length) * 100) -As [Int] + } + + Default + { + $FileDetails.PercentComplete = (100) -As [Int] + } + } + + $FileDetails.PercentComplete = [System.Math]::Round($FileDetails.PercentComplete, 2) + + $FileDetails.TotalSecondsElasped = $StopWatchTable.Secondary.Elapsed.TotalSeconds -As [Int] + + Switch ($FileDetails.TotalSecondsElasped -ne 0) + { + {($_ -eq $True)} + { + $FileDetails.TransferRate = ($FileDetails.TransferredBytes / $FileDetails.TotalSecondsElasped / 1MB) -As [Single] + } + + Default + { + $FileDetails.TransferRate = (0.0) -As [Single] + } + } + + $FileDetails.TransferRate = [System.Math]::Round($FileDetails.TransferRate, 2) + + Switch (($Total % 1MB) -eq 0) + { + {($_ -eq $True)} + { + Switch ($FileDetails.PercentComplete -gt 0) + { + {($_ -eq $True)} + { + $FileDetails.SecondsRemaining = ((($FileDetails.TotalSecondsElasped / $FileDetails.PercentComplete) * 100) - $FileDetails.TotalSecondsElasped) -As [Int] + } + + Default + { + $FileDetails.SecondsRemaining = (0) -As [Int] + } + } + + $FileListDetails.PercentComplete = [System.Math]::Round((($FileListDetails.TransferredBytes / $FileListDetails.TotalBytes) * 100), 2) + + $FileListDetails.TotalSecondsElasped = $StopWatchTable.Primary.Elapsed.TotalSeconds -As [Int] + + Switch ($FileListDetails.PercentComplete -gt 0) + { + {($_ -eq $True)} + { + $FileListDetails.SecondsRemaining = ((($FileListDetails.TotalSecondsElasped / $FileListDetails.PercentComplete) * 100) - $FileListDetails.TotalSecondsElasped) -As [Int] + } + + Default + { + $FileListDetails.SecondsRemaining = ((($FileListDetails.TotalSecondsElasped / $FileListDetails.PercentComplete) * 100) - $FileListDetails.TotalSecondsElasped) -As [Int] + } + } + + $TaskSequence = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary' + $TaskSequence.Environment = Try {New-Object -ComObject 'Microsoft.SMS.TSEnvironment'} Catch {$Null} + $TaskSequence.IsRunning = $Null -ine $TaskSequence.Environment + + Switch ($TaskSequence.IsRunning) + { + {($_ -eq $True)} + { + $FileProgressParameters = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary' + $FileProgressParameters.ID = 1 + $FileProgressParameters.Activity = "$($FileDetails.PercentComplete)% - Downloading $($FileSystemObject.GetFile($FileObjectProperties.Destination.FullName).ShortName) @ $($FileDetails.TransferRate) Mbps ($($FileDetails.Number) of $($FileListDetails.Count))" + $FileProgressParameters.Status = "$($FileDetails.PercentComplete)% - Downloading $($FileSystemObject.GetFile($FileObjectProperties.Destination.FullName).ShortName) @ $($FileDetails.TransferRate) Mbps ($($FileDetails.Number) of $($FileListDetails.Count))" + $FileProgressParameters.PercentComplete = $FileDetails.PercentComplete + $FileProgressParameters.SecondsRemaining = $FileDetails.SecondsRemaining + + Write-Progress @FileProgressParameters + } + + Default + { + $TotalProgressParameters = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary' + $TotalProgressParameters.ID = 1 + $TotalProgressParameters.Activity = "$($FileListDetails.PercentComplete)% - Copying file $($FileDetails.Number) of $($FileListDetails.Count) ($($FileListDetails.Count - $($FileListIndex)) left)" + $TotalProgressParameters.Status = $FileObjectProperties.Source.FullName + $TotalProgressParameters.PercentComplete = $FileListDetails.PercentComplete + $TotalProgressParameters.SecondsRemaining = $FileListDetails.SecondsRemaining + + Write-Progress @TotalProgressParameters + + $FileProgressParameters = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary' + $FileProgressParameters.ParentID = $TotalProgressParameters.ID + $FileProgressParameters.ID = $TotalProgressParameters.ID + 1 + $FileProgressParameters.Activity = "$($FileDetails.PercentComplete)% - Copying @ $($FileDetails.TransferRate) Mbps" + $FileProgressParameters.Status = $FileObjectProperties.Destination.FullName + $FileProgressParameters.PercentComplete = $FileDetails.PercentComplete + $FileProgressParameters.SecondsRemaining = $FileDetails.SecondsRemaining + + Write-Progress @FileProgressParameters + } + } + } + } + + Switch ($RandomDelay.IsPresent) + { + {($_ -eq $True)} + { + $Delay = Get-Random -Minimum 1 -Maximum 1500 + + $Null = Start-Sleep -Milliseconds ($Delay) + } + } + } + While ($FileDetails.SegmentBytes -gt 0) + + $Null = $FileDetails.Source.Close() + + $Null = $FileDetails.Destination.Close() + + $Null = $StopWatchTable.Secondary.Stop() + + $FileDetails = Get-Item -Path $FileObjectProperties.Destination.FullName -Force + + $FileAttributeList = $FileDetails.PSObject.Properties | Where-Object {($_.MemberType -iin @('NoteProperty', 'Property')) -and ($_.TypeNameOfValue -ieq 'System.DateTime') -and ($_.IsSettable -eq $True) -and ($_.Name -inotmatch '.*UTC.*')} + + ForEach ($FileAttribute In $FileAttributeList) + { + #$LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Attempting to update file attribute `"$($FileAttribute.Name)`" on file `"$($FileDetails.FullName)`" from `"$($FileDetails.$($FileAttribute.Name))`" to `"$($FileObjectProperties.Source.$($FileAttribute.Name))`". Please Wait..." + #Write-Verbose -Message ($LoggingDetails.LogMessage) + + $FileDetails.$($FileAttribute.Name) = $FileObjectProperties.Source.$($FileAttribute.Name) + } + + $FileObjectProperties.Destination = $FileDetails + + $FileObjectProperties.Status = 'Successful' + } + + Default + { + $LoggingDetails.WarningMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - File `"$($FileObjectProperties.Destination.FullName)`" already exists. Skipping." + Write-Verbose -Message ($LoggingDetails.WarningMessage) + + $FileObjectProperties.Status = 'Skipped' + } + } + } + Catch + { + $FileObjectProperties.Status = 'Error' + + $ErrorHandlingDefinition.Invoke() + } + Finally + { + $FileObjectProperties.TimeToTransfer = $StopWatchTable.Secondary.Elapsed + + $Null = $StopWatchTable.Secondary.Reset() + + $FileObject = New-Object -TypeName 'PSObject' -Property ($FileObjectProperties) + + $OutputObjectList.Add($FileObject) + } + } + + $Null = $StopWatchTable.Primary.Stop() + + $Null = $StopWatchTable.Primary.Reset() + + $FileListStatusGroups = $OutputObjectList | Group-Object -Property 'Status' | Sort-Object -Property @('Name') + + ForEach ($FileListStatusGroup In $FileListStatusGroups) + { + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - $($FileListStatusGroup.Count) of $($FileListDetails.Count) file(s) have a status of `"$($FileListStatusGroup.Name)`"." + Write-Verbose -Message ($LoggingDetails.LogMessage) -Verbose + } + } + + Default + { + $LoggingDetails.WarningMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - There are $($FileListDetails.Count) file(s) to copy to `"$($Destination.FullName)`". No further action will be taken." + Write-Verbose -Message ($LoggingDetails.WarningMessage) -Verbose + } + } + } + + Default + { + $LoggingDetails.WarningMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - There were 0 paths specified to search. No further action will be taken." + Write-Warning -Message ($LoggingDetails.WarningMessage) + } + } + } + Catch + { + $ErrorHandlingDefinition.Invoke() + } + Finally + { + $Null = Start-Sleep -Seconds 3 + } + } + + End + { + Try + { + #Determine the date and time the function completed execution + $FunctionEndTime = (Get-Date) + + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Execution of $($FunctionName) ended on $($FunctionEndTime.ToString($DateTimeLogFormat))" + Write-Verbose -Message ($LoggingDetails.LogMessage) -Verbose + + #Log the total script execution time + $FunctionExecutionTimespan = New-TimeSpan -Start ($FunctionStartTime) -End ($FunctionEndTime) + + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Function execution took $($FunctionExecutionTimespan.Hours.ToString()) hour(s), $($FunctionExecutionTimespan.Minutes.ToString()) minute(s), $($FunctionExecutionTimespan.Seconds.ToString()) second(s), and $($FunctionExecutionTimespan.Milliseconds.ToString()) millisecond(s)" + Write-Verbose -Message ($LoggingDetails.LogMessage) -Verbose + + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Function `'$($FunctionName)`' is completed." + Write-Verbose -Message ($LoggingDetails.LogMessage) -Verbose + } + Catch + { + $ErrorHandlingDefinition.Invoke() + } + Finally + { + #Write the object to the powershell pipeline + $OutputObjectList = $OutputObjectList.ToArray() + + Write-Output -InputObject ($OutputObjectList) + } + } + } +#endregion \ No newline at end of file diff --git a/Functions/Get-InstalledSoftware.ps1 b/Functions/Get-InstalledSoftware.ps1 new file mode 100644 index 0000000..da12016 --- /dev/null +++ b/Functions/Get-InstalledSoftware.ps1 @@ -0,0 +1,450 @@ +## Microsoft Function Naming Convention: http://msdn.microsoft.com/en-us/library/ms714428(v=vs.85).aspx + +#region Function Get-InstalledSoftware +Function Get-InstalledSoftware + { + <# + .SYNOPSIS + Retrieves a list of software installed on the current device. + + .DESCRIPTION + Supports filtering the resulting list of software by using regular expressions. + + .PARAMETER FilterInclusionExpression + Includes software based on their display name. + + .PARAMETER FilterInclusionExpression + Excludes software based on their display name. + + .PARAMETER ContinueOnError + Continues processing even if an error has occured. + + .EXAMPLE + Get-InstalledSoftware + + .EXAMPLE + Get-InstalledSoftware -Verbose + + .EXAMPLE + $GetInstalledSoftwareParameters = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary' + $GetInstalledSoftwareParameters.FilterInclusionExpression = "(.*)" + $GetInstalledSoftwareParameters.FilterExclusionExpression = "(^.{0,0}$)" + $GetInstalledSoftwareParameters.ContinueOnError = $False + $GetInstalledSoftwareParameters.Verbose = $True + + $GetInstalledSoftwareResult = Get-InstalledSoftware @GetInstalledSoftwareParameters + + Write-Output -InputObject ($GetInstalledSoftwareResult) + #> + + [CmdletBinding()] + + Param + ( + [Parameter(Mandatory=$False)] + [ValidateNotNullOrEmpty()] + [Alias('FIE')] + [Regex]$FilterInclusionExpression, + + [Parameter(Mandatory=$False)] + [ValidateNotNullOrEmpty()] + [Alias('FEE')] + [Regex]$FilterExclusionExpression, + + [Parameter(Mandatory=$False)] + [Alias('COE')] + [Switch]$ContinueOnError + ) + + Begin + { + Try + { + $DateTimeLogFormat = 'dddd, MMMM dd, yyyy @ hh:mm:ss.FFF tt' ###Monday, January 01, 2019 @ 10:15:34.000 AM### + [ScriptBlock]$GetCurrentDateTimeLogFormat = {(Get-Date).ToString($DateTimeLogFormat)} + $DateTimeMessageFormat = 'MM/dd/yyyy HH:mm:ss.FFF' ###03/23/2022 11:12:48.347### + [ScriptBlock]$GetCurrentDateTimeMessageFormat = {(Get-Date).ToString($DateTimeMessageFormat)} + $DateFileFormat = 'yyyyMMdd' ###20190403### + [ScriptBlock]$GetCurrentDateFileFormat = {(Get-Date).ToString($DateFileFormat)} + $DateTimeFileFormat = 'yyyyMMdd_HHmmss' ###20190403_115354### + [ScriptBlock]$GetCurrentDateTimeFileFormat = {(Get-Date).ToString($DateTimeFileFormat)} + $TextInfo = (Get-Culture).TextInfo + $LoggingDetails = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary' + $LoggingDetails.Add('LogMessage', $Null) + $LoggingDetails.Add('WarningMessage', $Null) + $LoggingDetails.Add('ErrorMessage', $Null) + $CommonParameterList = New-Object -TypeName 'System.Collections.Generic.List[String]' + $CommonParameterList.AddRange([System.Management.Automation.PSCmdlet]::CommonParameters) + $CommonParameterList.AddRange([System.Management.Automation.PSCmdlet]::OptionalCommonParameters) + + [ScriptBlock]$ErrorHandlingDefinition = { + $ErrorMessageList = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary' + $ErrorMessageList.Add('Message', $_.Exception.Message) + $ErrorMessageList.Add('Category', $_.Exception.ErrorRecord.FullyQualifiedErrorID) + $ErrorMessageList.Add('Script', $_.InvocationInfo.ScriptName) + $ErrorMessageList.Add('LineNumber', $_.InvocationInfo.ScriptLineNumber) + $ErrorMessageList.Add('LinePosition', $_.InvocationInfo.OffsetInLine) + $ErrorMessageList.Add('Code', $_.InvocationInfo.Line.Trim()) + + ForEach ($ErrorMessage In $ErrorMessageList.GetEnumerator()) + { + $LoggingDetails.ErrorMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - ERROR: $($ErrorMessage.Key): $($ErrorMessage.Value)" + Write-Warning -Message ($LoggingDetails.ErrorMessage) + } + + Switch (($ContinueOnError.IsPresent -eq $False) -or ($ContinueOnError -eq $False)) + { + {($_ -eq $True)} + { + Throw + } + } + } + + #Determine the date and time we executed the function + $FunctionStartTime = (Get-Date) + + [String]$FunctionName = $MyInvocation.MyCommand + [System.IO.FileInfo]$InvokingScriptPath = $MyInvocation.PSCommandPath + [System.IO.DirectoryInfo]$InvokingScriptDirectory = $InvokingScriptPath.Directory.FullName + [System.IO.FileInfo]$FunctionPath = "$($InvokingScriptDirectory.FullName)\Functions\$($FunctionName).ps1" + [System.IO.DirectoryInfo]$FunctionDirectory = "$($FunctionPath.Directory.FullName)" + + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Function `'$($FunctionName)`' is beginning. Please Wait..." + Write-Verbose -Message ($LoggingDetails.LogMessage) -Verbose + + #Define Default Action Preferences + $ErrorActionPreference = 'Stop' + + [String[]]$AvailableScriptParameters = (Get-Command -Name ($FunctionName)).Parameters.GetEnumerator() | Where-Object {($_.Value.Name -inotin $CommonParameterList)} | ForEach-Object {"-$($_.Value.Name):$($_.Value.ParameterType.Name)"} + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Available Function Parameter(s) = $($AvailableScriptParameters -Join ', ')" + Write-Verbose -Message ($LoggingDetails.LogMessage) -Verbose + + [String[]]$SuppliedScriptParameters = $PSBoundParameters.GetEnumerator() | ForEach-Object {"-$($_.Key):$($_.Value.GetType().Name)"} + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Supplied Function Parameter(s) = $($SuppliedScriptParameters -Join ', ')" + Write-Verbose -Message ($LoggingDetails.LogMessage) -Verbose + + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Execution of $($FunctionName) began on $($FunctionStartTime.ToString($DateTimeLogFormat))" + Write-Verbose -Message ($LoggingDetails.LogMessage) -Verbose + + #region Set Default Parameter Values + Switch ($True) + { + {([String]::IsNullOrEmpty($FilterInclusionExpression) -eq $True) -or ([String]::IsNullOrWhiteSpace($FilterInclusionExpression) -eq $True)} + { + [Regex]$FilterInclusionExpression = '(.*)' + } + + {([String]::IsNullOrEmpty($FilterExclusionExpression) -eq $True) -or ([String]::IsNullOrWhiteSpace($FilterExclusionExpression) -eq $True)} + { + [Regex]$FilterExclusionExpression = '(^.{0,0}$)' + } + } + #endregion + + #Create a table for the conversion of dates + $DateTimeProperties = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary' + $DateTimeProperties.FormatList = New-Object -TypeName 'System.Collections.Generic.List[String]' + $DateTimeProperties.FormatList.AddRange(([System.Globalization.DateTimeFormatInfo]::CurrentInfo.GetAllDateTimePatterns())) + $DateTimeProperties.FormatList.AddRange(([System.Globalization.DateTimeFormatInfo]::InvariantInfo.GetAllDateTimePatterns())) + $DateTimeProperties.FormatList.Add('yyyyMM') + $DateTimeProperties.FormatList.Add('yyyyMMdd') + $DateTimeProperties.Culture = $Null + $DateTimeProperties.Styles = New-Object -TypeName 'System.Collections.Generic.List[System.Globalization.DateTimeStyles]' + $DateTimeProperties.Styles.Add([System.Globalization.DateTimeStyles]::AssumeLocal) + $DateTimeProperties.Styles.Add([System.Globalization.DateTimeStyles]::AllowWhiteSpaces) + + #Create an object that will contain the functions output. + $OutputObjectList = New-Object -TypeName 'System.Collections.Generic.List[PSObject]' + } + Catch + { + $ErrorHandlingDefinition.Invoke() + } + Finally + { + + } + } + + Process + { + Try + { + $DateTimeLogFormat = 'dddd, MMMM dd, yyyy @ hh:mm:ss.FFF tt' ###Monday, January 01, 2019 @ 10:15:34.000 AM### + [ScriptBlock]$GetCurrentDateTimeLogFormat = {(Get-Date).ToString($DateTimeLogFormat)} + $DateTimeMessageFormat = 'MM/dd/yyyy HH:mm:ss.FFF' ###03/23/2022 11:12:48.347### + [ScriptBlock]$GetCurrentDateTimeMessageFormat = {(Get-Date).ToString($DateTimeMessageFormat)} + $DateFileFormat = 'yyyyMMdd' ###20190403### + [ScriptBlock]$GetCurrentDateFileFormat = {(Get-Date).ToString($DateFileFormat)} + $DateTimeFileFormat = 'yyyyMMdd_HHmmss' ###20190403_115354### + [ScriptBlock]$GetCurrentDateTimeFileFormat = {(Get-Date).ToString($DateTimeFileFormat)} + $TextInfo = (Get-Culture).TextInfo + $LoggingDetails = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary' + $LoggingDetails.Add('LogMessage', $Null) + $LoggingDetails.Add('WarningMessage', $Null) + $LoggingDetails.Add('ErrorMessage', $Null) + $CommonParameterList = New-Object -TypeName 'System.Collections.Generic.List[String]' + $CommonParameterList.AddRange([System.Management.Automation.PSCmdlet]::CommonParameters) + $CommonParameterList.AddRange([System.Management.Automation.PSCmdlet]::OptionalCommonParameters) + $RegularExpressionTable = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary' + $RegularExpressionTable.Base64 = '^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{4})$' -As [Regex] + $RegularExpressionTable.GUID = '(\{){0,1}[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}(\}){0,1}' -As [Regex] + $RegexOptionList = New-Object -TypeName 'System.Collections.Generic.List[System.Text.RegularExpressions.RegexOptions[]]' + $RegexOptionList.Add([System.Text.RegularExpressions.RegexOptions]::IgnoreCase) + $RegexOptionList.Add([System.Text.RegularExpressions.RegexOptions]::Multiline) + + [ScriptBlock]$ErrorHandlingDefinition = { + $ErrorMessageList = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary' + $ErrorMessageList.Add('Message', $_.Exception.Message) + $ErrorMessageList.Add('Category', $_.Exception.ErrorRecord.FullyQualifiedErrorID) + $ErrorMessageList.Add('Script', $_.InvocationInfo.ScriptName) + $ErrorMessageList.Add('LineNumber', $_.InvocationInfo.ScriptLineNumber) + $ErrorMessageList.Add('LinePosition', $_.InvocationInfo.OffsetInLine) + $ErrorMessageList.Add('Code', $_.InvocationInfo.Line.Trim()) + + ForEach ($ErrorMessage In $ErrorMessageList.GetEnumerator()) + { + $LoggingDetails.ErrorMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - ERROR: $($ErrorMessage.Key): $($ErrorMessage.Value)" + Write-Warning -Message ($LoggingDetails.ErrorMessage) + } + + Switch (($ContinueOnError.IsPresent -eq $False) -or ($ContinueOnError -eq $False)) + { + {($_ -eq $True)} + { + Throw + } + } + } + + $OutputObjectList = New-Object -TypeName 'System.Collections.Generic.List[PSObject]' + + $OutputObjectValueList = New-Object -TypeName 'System.Collections.Generic.List[String]' + $OutputObjectValueList.Add('DisplayName') + $OutputObjectValueList.Add('DisplayVersion') + $OutputObjectValueList.Add('UninstallString') + $OutputObjectValueList.Add('InstallLocation') + $OutputObjectValueList.Add('Publisher') + $OutputObjectValueList.Add('InstallDate') + + $RegistryHiveList = New-Object -TypeName 'System.Collections.Generic.List[PSObject]' + + $RegistryHiveProperties = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary' + $RegistryHiveProperties.Add('Type', [Microsoft.Win32.RegistryHive]::LocalMachine) + $RegistryHiveProperties.Add('KeyList', (New-Object -TypeName 'System.Collections.Generic.List[String]')) + $RegistryHiveProperties.KeyList.Add('Software\Microsoft\Windows\CurrentVersion\Uninstall') + $RegistryHiveProperties.KeyList.Add('Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall') + $RegistryHiveObject = New-Object -TypeName 'PSObject' -Property ($RegistryHiveProperties) + $RegistryHiveList.Add($RegistryHiveObject) + + $RegistryHiveProperties = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary' + $RegistryHiveProperties.Add('Type', [Microsoft.Win32.RegistryHive]::CurrentUser) + $RegistryHiveProperties.Add('KeyList', (New-Object -TypeName 'System.Collections.Generic.List[String]')) + $RegistryHiveProperties.KeyList.Add('Software\Microsoft\Windows\CurrentVersion\Uninstall') + $RegistryHiveObject = New-Object -TypeName 'PSObject' -Property ($RegistryHiveProperties) + $RegistryHiveList.Add($RegistryHiveObject) + + For ($RegistryHiveListIndex = 0; $RegistryHiveListIndex -lt $RegistryHiveList.Count; $RegistryHiveListIndex++) + { + $RegistryHive = $RegistryHiveList[$RegistryHiveListIndex] + + $RegistryHiveObject = [Microsoft.Win32.RegistryKey]::OpenBaseKey($RegistryHive.Type, [Microsoft.Win32.RegistryView]::Default) + + For ($KeyListIndex = 0; $KeyListIndex -lt $RegistryHive.KeyList.Count; $KeyListIndex++) + { + $RegistryKey = $RegistryHive.KeyList[$KeyListIndex] + + $RegistryKeyObject = $RegistryHiveObject.OpenSubKey($RegistryKey) + + Switch ($Null -ine $RegistryKeyObject) + { + {($_ -eq $True)} + { + $SubKeyNameList = $RegistryKeyObject.GetSubKeyNames() | Sort-Object + + For ($SubKeyNameListIndex = 0; $SubKeyNameListIndex -lt $SubKeyNameList.Count; $SubKeyNameListIndex++) + { + Try + { + $SubKeyName = $SubKeyNameList[$SubKeyNameListIndex] + + $SubKeyObject = $RegistryKeyObject.OpenSubKey($SubKeyName) + + $SubKeyObjectSegments = $SubKeyObject.Name.Split('\') + + $OutputObjectProperties = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary' + $OutputObjectProperties.Path = $SubKeyObject.Name + $OutputObjectProperties.Hive = $SubKeyObjectSegments[0] + $OutputObjectProperties.Location = ($SubKeyObjectSegments[1..$($SubKeyObjectSegments.GetUpperBound(0))]) -Join '\' + + + + ForEach ($OutputObjectValueName In $OutputObjectValueList) + { + $OutputObjectProperties.$($OutputObjectValueName) = $Null + } + + $ValueNameList = $SubKeyObject.GetValueNames() | Sort-Object + + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Now processing entry `"$($OutputObjectProperties.Path)`" [ValueCount: $($ValueNameList.Count)]. Please Wait..." + Write-Verbose -Message ($LoggingDetails.LogMessage) + + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Value Name List: $(($ValueNameList | Sort-Object) -Join '; ')" + Write-Verbose -Message ($LoggingDetails.LogMessage) + + Switch ($ValueNameList.Count -gt 0) + { + {($_ -eq $True)} + { + For ($ValueNameListIndex = 0; $ValueNameListIndex -lt $ValueNameList.Count; $ValueNameListIndex++) + { + $ValueName = $ValueNameList[$ValueNameListIndex] + + $ValueKind = $SubKeyObject.GetValueKind($ValueName) + + Switch ($ValueKind) + { + {($_ -ieq 'PlaceHolder')} + { + + } + + Default + { + $Value = $SubKeyObject.GetValue($ValueName) + } + } + + $OutputObjectProperties.$ValueName = $Value + } + + $OutputObjectProperties.ProductCode = Try {[Regex]::Match($OutputObjectProperties.Location, $RegularExpressionTable.GUID.ToString(), $RegexOptionList.ToArray()).Value.Trim()} Catch {$Null} + + Switch (($OutputObjectProperties.DisplayName -imatch $FilterInclusionExpression.ToString()) -and ($OutputObjectProperties.DisplayName -inotmatch $FilterExclusionExpression.ToString())) + { + {($_ -eq $True)} + { + ForEach ($OutputObjectProperty In ($OutputObjectProperties.Keys | Sort-Object)) + { + $OutputObjectPropertyName = $OutputObjectProperty + + $OutputObjectPropertyValue = $OutputObjectProperties.$($OutputObjectPropertyName) + + Switch ($OutputObjectPropertyName) + { + {($_ -iin @('DisplayVersion'))} + { + $OutputObjectPropertyValue = Try {New-Object -TypeName 'System.Version' -ArgumentList ($OutputObjectPropertyValue)} Catch {$OutputObjectPropertyValue} + } + + {($_ -iin @('InstallDate'))} + { + $DateTime = New-Object -TypeName 'DateTime' + + $DateTimeProperties.Input = $OutputObjectPropertyValue + $DateTimeProperties.Successful = [DateTime]::TryParseExact($DateTimeProperties.Input, $DateTimeProperties.FormatList, $DateTimeProperties.Culture, $DateTimeProperties.Styles.ToArray(), [Ref]$DateTime) + $DateTimeProperties.DateTime = $DateTime + + $DateTimeObject = New-Object -TypeName 'PSObject' -Property ($DateTimeProperties) + + Switch ($DateTimeObject.Successful) + { + {($_ -eq $True)} + { + $OutputObjectPropertyValue = $DateTimeObject.DateTime + } + } + } + + Default + { + Switch ($OutputObjectPropertyValue) + { + {($_ -imatch '(^\d+$)')} + { + $OutputObjectPropertyValue = $OutputObjectPropertyValue -As [Int32] + } + } + } + } + + $OutputObjectProperties.Remove($OutputObjectPropertyName) + + $OutputObjectProperties.$($OutputObjectPropertyName) = $OutputObjectPropertyValue + } + + $OutputObject = New-Object -TypeName 'PSObject' -Property ($OutputObjectProperties) + + $OutputObjectList.Add($OutputObject) + } + } + } + } + + Try {$Null = $SubKeyObject.Close()} Catch {} + } + Catch + { + + } + Finally + { + + } + } + } + } + + Try {$Null = $RegistryKeyObject.Close()} Catch {} + } + + Try {$Null = $RegistryHiveObject.Close()} Catch {} + } + } + Catch + { + $ErrorHandlingDefinition.Invoke() + } + Finally + { + + } + } + + End + { + Try + { + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Found $($OutputObjectList.Count) instances of software matching the specified regular expression(s)." + Write-Verbose -Message ($LoggingDetails.LogMessage) -Verbose + + #Determine the date and time the function completed execution + $FunctionEndTime = (Get-Date) + + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Execution of $($FunctionName) ended on $($FunctionEndTime.ToString($DateTimeLogFormat))" + Write-Verbose -Message ($LoggingDetails.LogMessage) -Verbose + + #Log the total script execution time + $FunctionExecutionTimespan = New-TimeSpan -Start ($FunctionStartTime) -End ($FunctionEndTime) + + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Function execution took $($FunctionExecutionTimespan.Hours.ToString()) hour(s), $($FunctionExecutionTimespan.Minutes.ToString()) minute(s), $($FunctionExecutionTimespan.Seconds.ToString()) second(s), and $($FunctionExecutionTimespan.Milliseconds.ToString()) millisecond(s)" + Write-Verbose -Message ($LoggingDetails.LogMessage) -Verbose + + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Function `'$($FunctionName)`' is completed." + Write-Verbose -Message ($LoggingDetails.LogMessage) -Verbose + } + Catch + { + $ErrorHandlingDefinition.Invoke() + } + Finally + { + #Write the object to the powershell pipeline + $OutputObjectList = $OutputObjectList.ToArray() + + Write-Output -InputObject ($OutputObjectList) + } + } + } +#endregion \ No newline at end of file diff --git a/Functions/Get-MSIProductList.ps1 b/Functions/Get-MSIProductList.ps1 new file mode 100644 index 0000000..1e7a60a --- /dev/null +++ b/Functions/Get-MSIProductList.ps1 @@ -0,0 +1,41 @@ +Function Get-MSIProductList + { + $OutputObjectList = New-Object -TypeName 'System.Collections.Generic.List[PSObject]' + + $ComObject = New-Object -ComObject 'WindowsInstaller.Installer' + + $ComObjectType = $ComObject.GetType() + + [String[]]$AttributeList = @('Language', 'ProductName', 'PackageCode', 'Transforms', 'AssignmentType', 'PackageName', 'InstalledProductName', 'VersionString', 'RegCompany', 'RegOwner', 'ProductID', 'ProductIcon', 'InstallLocation', 'InstallSource', 'InstallDate', 'Publisher', 'LocalPackage', 'HelpLink', 'HelpTelephone', 'URLInfoAbout', 'URLUpdateInfo') | Sort-Object + + $ProductList = $ComObjectType.InvokeMember('Products', [System.Reflection.BindingFlags]::GetProperty, $Null, $ComObject, $Null) + + ForEach ($Product In $ProductList) + { + $OutputObjectProperties = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary' + $OutputObjectProperties.Add('ProductCode', $Product) + + For ($AttributeListIndex = 0; $AttributeListIndex -lt $AttributeList.Count; $AttributeListIndex++) + { + [String]$AttributeName = $AttributeList[$AttributeListIndex] + + Switch ($OutputObjectProperties.Contains($AttributeName)) + { + {($_ -eq $False)} + { + $OutputObjectProperties.Add($AttributeName, $Null) + } + } + + Try {$OutputObjectProperties."$($AttributeName)" = $ComObjectType.InvokeMember('ProductInfo', [System.Reflection.BindingFlags]::GetProperty, $Null, $ComObject, @($Product, $AttributeName))} Catch {} + } + + $OutputObject = New-Object -TypeName 'PSObject' -Property ($OutputObjectProperties) + + $Null = $OutputObjectList.Add($OutputObject) + } + + Write-Output -InputObject ($OutputObjectList) + } + +#Get-MSIProductList \ No newline at end of file diff --git a/Functions/Get-MSIPropertyList.ps1 b/Functions/Get-MSIPropertyList.ps1 new file mode 100644 index 0000000..fb328cc --- /dev/null +++ b/Functions/Get-MSIPropertyList.ps1 @@ -0,0 +1,91 @@ +Function Get-MSIPropertyList + { + [CmdletBinding()] + Param + ( + [Parameter(Mandatory=$True)] + [ValidateNotNullOrEmpty()] + [ValidateScript({(Test-Path -Path $_)})] + [System.IO.FileInfo[]]$Path + ) + + Begin + { + $OutputObjectList = New-Object -TypeName 'System.Collections.Generic.List[PSObject]' + } + + Process + { + Try + { + ForEach ($Item In $Path) + { + $ComObject = New-Object -ComObject 'WindowsInstaller.Installer' + + $MSIDatabase = $ComObject.GetType().InvokeMember('OpenDatabase', 'InvokeMethod', $Null, $ComObject, @($Item.FullName, 0)) + + [String]$Query = 'SELECT * FROM Property' + + $View = $MSIDatabase.GetType().InvokeMember('OpenView', 'InvokeMethod', $Null, $MSIDatabase, $Query) + $View.GetType().InvokeMember('Execute', 'InvokeMethod', $Null, $View, $Null) + + $OutputObjectProperties = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary' + $OutputObjectProperties.Add('Path', $Item) + + While ($Record = $View.GetType().InvokeMember('Fetch', 'InvokeMethod', $Null, $View, $Null)) + { + Switch ($Null -ine $Record) + { + {($_ -eq $True)} + { + [String]$MSIPropertyName = $Record.GetType().InvokeMember('StringData', 'GetProperty', $Null, $Record, 1) + [String]$MSIPropertyValue = $Record.GetType().InvokeMember('StringData', 'GetProperty', $Null, $Record, 2) + + Switch ($OutputObjectProperties.Contains($MSIPropertyName)) + { + {($_ -eq $False)} + { + $OutputObjectProperties.Add($MSIPropertyName, $MSIPropertyValue) + } + } + } + } + } + + $OutputObject = New-Object -TypeName 'PSObject' -Property ($OutputObjectProperties) + + $OutputObjectList.Add($OutputObject) + + $Null = [System.Runtime.InteropServices.Marshal]::ReleaseComObject($MSIDatabase) + + $Null = [System.Runtime.InteropServices.Marshal]::ReleaseComObject($ComObject) + } + } + Catch + { + $ErrorMessageList = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary' + $ErrorMessageList.Add('ErrorMessage', $_.Exception.Message) + $ErrorMessageList.Add('Command', $_.InvocationInfo.MyCommand.Name) + $ErrorMessageList.Add('LineNumber', $_.InvocationInfo.ScriptLineNumber) + $ErrorMessageList.Add('LinePosition', $_.InvocationInfo.OffsetInLine) + $ErrorMessageList.Add('Code', $_.InvocationInfo.Line.Trim()) + + ForEach ($ErrorMessage In $ErrorMessageList.GetEnumerator()) + { + $ErrorMessage = "$($ErrorMessage.Key): $($ErrorMessage.Value)" + Write-Warning -Message ($ErrorMessage) -Verbose + } + } + Finally + { + $OutputObjectList = $OutputObjectList.ToArray() + + Write-Output -InputObject ($OutputObjectList) + } + } + + End + { + + } + } \ No newline at end of file diff --git a/Functions/Get-UserSessionList.ps1 b/Functions/Get-UserSessionList.ps1 new file mode 100644 index 0000000..bdac949 --- /dev/null +++ b/Functions/Get-UserSessionList.ps1 @@ -0,0 +1,538 @@ +## Microsoft Function Naming Convention: http://msdn.microsoft.com/en-us/library/ms714428(v=vs.85).aspx + +#region Function Get-UserSessionList +Function Get-UserSessionList + { + [CmdletBinding()] + Param + ( + [ValidateNotNullOrEmpty()] + [String]$ComputerName = "$($Env:ComputerName)" + ) + + <# + .SYNOPSIS + Get session details for all local and RDP logged on users. + .DESCRIPTION + Get session details for all local and RDP logged on users using Win32 APIs. Get the following session details: + NTAccount, SID, UserName, DomainName, SessionId, SessionName, ConnectState, IsCurrentSession, IsConsoleSession, IsUserSession, IsActiveUserSession + IsRdpSession, IsLocalAdmin, LogonTime, IdleTime, DisconnectTime, ClientName, ClientProtocolType, ClientDirectory, ClientBuildNumber + .EXAMPLE + Get-LoggedOnUser + .NOTES + Description of ConnectState property: + Value Description + ----- ----------- + Active A user is logged on to the session. + ConnectQuery The session is in the process of connecting to a client. + Connected A client is connected to the session. + Disconnected The session is active, but the client has disconnected from it. + Down The session is down due to an error. + Idle The session is waiting for a client to connect. + Initializing The session is initializing. + Listening The session is listening for connections. + Reset The session is being reset. + Shadowing This session is shadowing another session. + + Description of IsActiveUserSession property: + If a console user exists, then that will be the active user session. + If no console user exists but users are logged in, such as on terminal servers, then the first logged-in non-console user that is either 'Active' or 'Connected' is the active user. + + Description of IsRdpSession property: + Gets a value indicating whether the user is associated with an RDP client session. + .LINK + http://psappdeploytoolkit.com + #> + + $TypeDefinition = + @" +// Date Modified: 01-08-2016 +// Version Number: 3.6.8 + +using System; +using System.Text; +using System.Collections; +using System.ComponentModel; +using System.DirectoryServices; +using System.Security.Principal; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Text.RegularExpressions; +using FILETIME = System.Runtime.InteropServices.ComTypes.FILETIME; + +namespace PSADT +{ + public class QueryUser + { + [DllImport("wtsapi32.dll", CharSet = CharSet.Auto, SetLastError = false)] + public static extern IntPtr WTSOpenServer(string pServerName); + + [DllImport("wtsapi32.dll", CharSet = CharSet.Auto, SetLastError = false)] + public static extern void WTSCloseServer(IntPtr hServer); + + [DllImport("wtsapi32.dll", CharSet = CharSet.Ansi, SetLastError = false)] + public static extern bool WTSQuerySessionInformation(IntPtr hServer, int sessionId, WTS_INFO_CLASS wtsInfoClass, out IntPtr pBuffer, out int pBytesReturned); + + [DllImport("wtsapi32.dll", CharSet = CharSet.Ansi, SetLastError = false)] + public static extern int WTSEnumerateSessions(IntPtr hServer, int Reserved, int Version, out IntPtr pSessionInfo, out int pCount); + + [DllImport("wtsapi32.dll", CharSet = CharSet.Auto, SetLastError = false)] + public static extern void WTSFreeMemory(IntPtr pMemory); + + [DllImport("winsta.dll", CharSet = CharSet.Auto, SetLastError = false)] + public static extern int WinStationQueryInformation(IntPtr hServer, int sessionId, int information, ref WINSTATIONINFORMATIONW pBuffer, int bufferLength, ref int returnedLength); + + [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = false)] + public static extern int GetCurrentProcessId(); + + [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = false)] + public static extern bool ProcessIdToSessionId(int processId, ref int pSessionId); + + public class TerminalSessionData + { + public int SessionId; + public string ConnectionState; + public string SessionName; + public bool IsUserSession; + public TerminalSessionData(int sessionId, string connState, string sessionName, bool isUserSession) + { + SessionId = sessionId; + ConnectionState = connState; + SessionName = sessionName; + IsUserSession = isUserSession; + } + } + + public class TerminalSessionInfo + { + public string NTAccount; + public string SID; + public string UserName; + public string DomainName; + public int SessionId; + public string SessionName; + public string ConnectState; + public bool IsCurrentSession; + public bool IsConsoleSession; + public bool IsActiveUserSession; + public bool IsUserSession; + public bool IsRdpSession; + public bool IsLocalAdmin; + public DateTime? LogonTime; + public TimeSpan? IdleTime; + public DateTime? DisconnectTime; + public string ClientName; + public string ClientProtocolType; + public string ClientDirectory; + public int ClientBuildNumber; + } + + [StructLayout(LayoutKind.Sequential)] + private struct WTS_SESSION_INFO + { + public Int32 SessionId; + [MarshalAs(UnmanagedType.LPStr)] + public string SessionName; + public WTS_CONNECTSTATE_CLASS State; + } + + [StructLayout(LayoutKind.Sequential)] + public struct WINSTATIONINFORMATIONW + { + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 70)] + private byte[] Reserved1; + public int SessionId; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + private byte[] Reserved2; + public FILETIME ConnectTime; + public FILETIME DisconnectTime; + public FILETIME LastInputTime; + public FILETIME LoginTime; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1096)] + private byte[] Reserved3; + public FILETIME CurrentTime; + } + + public enum WINSTATIONINFOCLASS + { + WinStationInformation = 8 + } + + public enum WTS_CONNECTSTATE_CLASS + { + Active, + Connected, + ConnectQuery, + Shadow, + Disconnected, + Idle, + Listen, + Reset, + Down, + Init + } + + public enum WTS_INFO_CLASS + { + SessionId=4, + UserName, + SessionName, + DomainName, + ConnectState, + ClientBuildNumber, + ClientName, + ClientDirectory, + ClientProtocolType=16 + } + + private static IntPtr OpenServer(string Name) + { + IntPtr server = WTSOpenServer(Name); + return server; + } + + private static void CloseServer(IntPtr ServerHandle) + { + WTSCloseServer(ServerHandle); + } + + private static IList PtrToStructureList(IntPtr ppList, int count) where T : struct + { + List result = new List(); + long pointer = ppList.ToInt64(); + int sizeOf = Marshal.SizeOf(typeof(T)); + + for (int index = 0; index < count; index++) + { + T item = (T) Marshal.PtrToStructure(new IntPtr(pointer), typeof(T)); + result.Add(item); + pointer += sizeOf; + } + return result; + } + + public static DateTime? FileTimeToDateTime(FILETIME ft) + { + if (ft.dwHighDateTime == 0 && ft.dwLowDateTime == 0) + { + return null; + } + long hFT = (((long) ft.dwHighDateTime) << 32) + ft.dwLowDateTime; + return DateTime.FromFileTime(hFT); + } + + public static WINSTATIONINFORMATIONW GetWinStationInformation(IntPtr server, int sessionId) + { + int retLen = 0; + WINSTATIONINFORMATIONW wsInfo = new WINSTATIONINFORMATIONW(); + WinStationQueryInformation(server, sessionId, (int) WINSTATIONINFOCLASS.WinStationInformation, ref wsInfo, Marshal.SizeOf(typeof(WINSTATIONINFORMATIONW)), ref retLen); + return wsInfo; + } + + public static TerminalSessionData[] ListSessions(string ServerName) + { + IntPtr server = IntPtr.Zero; + if (ServerName == "localhost" || ServerName == String.Empty) + { + ServerName = Environment.MachineName; + } + + List results = new List(); + + try + { + server = OpenServer(ServerName); + IntPtr ppSessionInfo = IntPtr.Zero; + int count; + bool _isUserSession = false; + IList sessionsInfo; + + if (WTSEnumerateSessions(server, 0, 1, out ppSessionInfo, out count) == 0) + { + throw new Win32Exception(); + } + + try + { + sessionsInfo = PtrToStructureList(ppSessionInfo, count); + } + finally + { + WTSFreeMemory(ppSessionInfo); + } + + foreach (WTS_SESSION_INFO sessionInfo in sessionsInfo) + { + if (sessionInfo.SessionName != "Services" && sessionInfo.SessionName != "RDP-Tcp") + { + _isUserSession = true; + } + results.Add(new TerminalSessionData(sessionInfo.SessionId, sessionInfo.State.ToString(), sessionInfo.SessionName, _isUserSession)); + _isUserSession = false; + } + } + finally + { + CloseServer(server); + } + + TerminalSessionData[] returnData = results.ToArray(); + return returnData; + } + + public static TerminalSessionInfo GetSessionInfo(string ServerName, int SessionId) + { + IntPtr server = IntPtr.Zero; + IntPtr buffer = IntPtr.Zero; + int bytesReturned; + TerminalSessionInfo data = new TerminalSessionInfo(); + bool _IsCurrentSessionId = false; + bool _IsConsoleSession = false; + bool _IsUserSession = false; + int currentSessionID = 0; + string _NTAccount = String.Empty; + if (ServerName == "localhost" || ServerName == String.Empty) + { + ServerName = Environment.MachineName; + } + if (ProcessIdToSessionId(GetCurrentProcessId(), ref currentSessionID) == false) + { + currentSessionID = -1; + } + + // Get all members of the local administrators group + bool _IsLocalAdminCheckSuccess = false; + List localAdminGroupSidsList = new List(); + try + { + DirectoryEntry localMachine = new DirectoryEntry("WinNT://" + ServerName + ",Computer"); + string localAdminGroupName = new SecurityIdentifier("S-1-5-32-544").Translate(typeof(NTAccount)).Value.Split('\\')[1]; + DirectoryEntry admGroup = localMachine.Children.Find(localAdminGroupName, "group"); + object members = admGroup.Invoke("members", null); + foreach (object groupMember in (IEnumerable)members) + { + DirectoryEntry member = new DirectoryEntry(groupMember); + if (member.Name != String.Empty) + { + localAdminGroupSidsList.Add((new NTAccount(member.Name)).Translate(typeof(SecurityIdentifier)).Value); + } + } + _IsLocalAdminCheckSuccess = true; + } + catch { } + + try + { + server = OpenServer(ServerName); + + if (WTSQuerySessionInformation(server, SessionId, WTS_INFO_CLASS.ClientBuildNumber, out buffer, out bytesReturned) == false) + { + return data; + } + int lData = Marshal.ReadInt32(buffer); + data.ClientBuildNumber = lData; + + if (WTSQuerySessionInformation(server, SessionId, WTS_INFO_CLASS.ClientDirectory, out buffer, out bytesReturned) == false) + { + return data; + } + string strData = Marshal.PtrToStringAnsi(buffer); + data.ClientDirectory = strData; + + if (WTSQuerySessionInformation(server, SessionId, WTS_INFO_CLASS.ClientName, out buffer, out bytesReturned) == false) + { + return data; + } + strData = Marshal.PtrToStringAnsi(buffer); + data.ClientName = strData; + + if (WTSQuerySessionInformation(server, SessionId, WTS_INFO_CLASS.ClientProtocolType, out buffer, out bytesReturned) == false) + { + return data; + } + Int16 intData = Marshal.ReadInt16(buffer); + if (intData == 2) + { + strData = "RDP"; + data.IsRdpSession = true; + } + else + { + strData = ""; + data.IsRdpSession = false; + } + data.ClientProtocolType = strData; + + if (WTSQuerySessionInformation(server, SessionId, WTS_INFO_CLASS.ConnectState, out buffer, out bytesReturned) == false) + { + return data; + } + lData = Marshal.ReadInt32(buffer); + data.ConnectState = ((WTS_CONNECTSTATE_CLASS) lData).ToString(); + + if (WTSQuerySessionInformation(server, SessionId, WTS_INFO_CLASS.SessionId, out buffer, out bytesReturned) == false) + { + return data; + } + lData = Marshal.ReadInt32(buffer); + data.SessionId = lData; + + if (WTSQuerySessionInformation(server, SessionId, WTS_INFO_CLASS.DomainName, out buffer, out bytesReturned) == false) + { + return data; + } + strData = Marshal.PtrToStringAnsi(buffer).ToUpper(); + data.DomainName = strData; + if (strData != String.Empty) + { + _NTAccount = strData; + } + + if (WTSQuerySessionInformation(server, SessionId, WTS_INFO_CLASS.UserName, out buffer, out bytesReturned) == false) + { + return data; + } + strData = Marshal.PtrToStringAnsi(buffer); + data.UserName = strData; + if (strData != String.Empty) + { + data.NTAccount = _NTAccount + "\\" + strData; + string _Sid = (new NTAccount(_NTAccount + "\\" + strData)).Translate(typeof(SecurityIdentifier)).Value; + data.SID = _Sid; + if (_IsLocalAdminCheckSuccess == true) + { + foreach (string localAdminGroupSid in localAdminGroupSidsList) + { + if (localAdminGroupSid == _Sid) + { + data.IsLocalAdmin = true; + break; + } + else + { + data.IsLocalAdmin = false; + } + } + } + } + + if (WTSQuerySessionInformation(server, SessionId, WTS_INFO_CLASS.SessionName, out buffer, out bytesReturned) == false) + { + return data; + } + strData = Marshal.PtrToStringAnsi(buffer); + data.SessionName = strData; + if (strData != "Services" && strData != "RDP-Tcp" && data.UserName != String.Empty) + { + _IsUserSession = true; + } + data.IsUserSession = _IsUserSession; + if (strData == "Console") + { + _IsConsoleSession = true; + } + data.IsConsoleSession = _IsConsoleSession; + + WINSTATIONINFORMATIONW wsInfo = GetWinStationInformation(server, SessionId); + DateTime? _loginTime = FileTimeToDateTime(wsInfo.LoginTime); + DateTime? _lastInputTime = FileTimeToDateTime(wsInfo.LastInputTime); + DateTime? _disconnectTime = FileTimeToDateTime(wsInfo.DisconnectTime); + DateTime? _currentTime = FileTimeToDateTime(wsInfo.CurrentTime); + TimeSpan? _idleTime = (_currentTime != null && _lastInputTime != null) ? _currentTime.Value - _lastInputTime.Value : TimeSpan.Zero; + data.LogonTime = _loginTime; + data.IdleTime = _idleTime; + data.DisconnectTime = _disconnectTime; + + if (currentSessionID == SessionId) + { + _IsCurrentSessionId = true; + } + data.IsCurrentSession = _IsCurrentSessionId; + } + finally + { + WTSFreeMemory(buffer); + buffer = IntPtr.Zero; + CloseServer(server); + } + return data; + } + + public static TerminalSessionInfo[] GetUserSessionInfo(string ServerName) + { + if (ServerName == "localhost" || ServerName == String.Empty) + { + ServerName = Environment.MachineName; + } + + // Find and get detailed information for all user sessions + // Also determine the active user session. If a console user exists, then that will be the active user session. + // If no console user exists but users are logged in, such as on terminal servers, then select the first logged-in non-console user that is either 'Active' or 'Connected' as the active user. + TerminalSessionData[] sessions = ListSessions(ServerName); + TerminalSessionInfo sessionInfo = new TerminalSessionInfo(); + List userSessionsInfo = new List(); + string firstActiveUserNTAccount = String.Empty; + bool IsActiveUserSessionSet = false; + foreach (TerminalSessionData session in sessions) + { + if (session.IsUserSession == true) + { + sessionInfo = GetSessionInfo(ServerName, session.SessionId); + if (sessionInfo.IsUserSession == true) + { + if ((firstActiveUserNTAccount == String.Empty) && (sessionInfo.ConnectState == "Active" || sessionInfo.ConnectState == "Connected")) + { + firstActiveUserNTAccount = sessionInfo.NTAccount; + } + + if (sessionInfo.IsConsoleSession == true) + { + sessionInfo.IsActiveUserSession = true; + IsActiveUserSessionSet = true; + } + else + { + sessionInfo.IsActiveUserSession = false; + } + + userSessionsInfo.Add(sessionInfo); + } + } + } + + TerminalSessionInfo[] userSessions = userSessionsInfo.ToArray(); + if (IsActiveUserSessionSet == false) + { + foreach (TerminalSessionInfo userSession in userSessions) + { + if (userSession.NTAccount == firstActiveUserNTAccount) + { + userSession.IsActiveUserSession = true; + break; + } + } + } + + return userSessions; + } + } +} +"@ + + ## Add the custom types required for the toolkit + If (-not ([Management.Automation.PSTypeName]'PSADT.UiAutomation').Type) + { + [string[]]$ReferencedAssemblies = 'System.Drawing', 'System.Windows.Forms', 'System.DirectoryServices' + Add-Type -TypeDefinition $TypeDefinition -ReferencedAssemblies $ReferencedAssemblies -IgnoreWarnings -ErrorAction 'Stop' + } + + Try + { + Write-Output -InputObject ([PSADT.QueryUser]::GetUserSessionInfo("$($ComputerName)")) + } + Catch + { + Write-Warning -Message "Unable to communicate with `'$($ComputerName)`'" + } + } +#endregion \ No newline at end of file diff --git a/Functions/Invoke-FileDownloadWithProgress.ps1 b/Functions/Invoke-FileDownloadWithProgress.ps1 new file mode 100644 index 0000000..77a7efd --- /dev/null +++ b/Functions/Invoke-FileDownloadWithProgress.ps1 @@ -0,0 +1,565 @@ +## Microsoft Function Naming Convention: http://msdn.microsoft.com/en-us/library/ms714428(v=vs.85).aspx + +#region Function Invoke-FileDownloadWithProgress +Function Invoke-FileDownloadWithProgress + { + <# + .SYNOPSIS + Downloads the specified URL. + + .DESCRIPTION + The file will only be downloaded if the last modified date of the source URL is different from the last modified date of the file that has already been downloaded or if the file have not already been downloaded. + + .PARAMETER URL + The URL where the file is located. + + .PARAMETER Destination + The directory path where the URL will be downloaded to. If not specified, a default value will be used. + + .EXAMPLE + Invoke-FileDownloadWithProgress -URL 'https://dl.dell.com/catalog/DriverPackCatalog.cab' -Destination "$($Env:ProgramData)\Dell\DriverPackCatalog" -FileName "DriverPackCatalog.cab" -Verbose + + .EXAMPLE + $DownloadDetails = Invoke-FileDownloadWithProgress -URL 'https://dl.dell.com/catalog/DriverPackCatalog.cab' -Destination "$($Env:ProgramData)\Dell\DriverPackCatalog" -Verbose + + Write-Output -InputObject ($DownloadDetails) + + .EXAMPLE + $InvokeFileDownloadWithProgressParameters = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary' + $InvokeFileDownloadWithProgressParameters.URL = 'https://dl.dell.com/catalog/DriverPackCatalog.cab' -As [System.URI] + $InvokeFileDownloadWithProgressParameters.Destination = "$($Env:ProgramData)\Dell\DriverPackCatalog" -As [System.IO.DirectoryInfo] + $InvokeFileDownloadWithProgressParameters.FileName = "DriverPackCatalog.cab" + $InvokeFileDownloadWithProgressParameters.ContinueOnError = $False + $InvokeFileDownloadWithProgressParameters.Verbose = $True + + $InvokeFileDownloadWithProgressResult = Invoke-FileDownloadWithProgress @InvokeFileDownloadWithProgressParameters + + Write-Output -InputObject ($InvokeFileDownloadWithProgressResult) + + .NOTES + NEL : {"report_to":"network-errors","max_age":3600} + Report-To : {"group":"network-errors","max_age":3600,"endpoints":[{"url":"https://www.dell.com/support/onlineapi/nellogger/log"}]} + Accept-Ranges : bytes + Content-Type : application/vnd.ms-cab-compressed + ETag : "8043933683ddd81:0" + Last-Modified : Tue, 11 Oct 2022 15:07:43 GMT + Server : Microsoft-IIS/10.0 + X-Powered-By : ASP.NET + x-arr-set : arr4 + Content-Length : 270867 + Date : Thu, 20 Oct 2022 15:51:44 GMT + Connection : keep-alive + Akamai-Request-BC : [a=23.207.199.174,b=78861523,c=g,n=US_VA_STERLING,o=20940] + + .LINK + https://learn.microsoft.com/en-us/dotnet/api/system.net.webrequest?view=netframework-4.8 + + .LINK + https://learn.microsoft.com/en-us/dotnet/api/system.net.webclient.downloadfiletaskasync?view=netframework-4.8 + #> + + [CmdletBinding(ConfirmImpact = 'Low', SupportsShouldProcess = $True)] + + Param + ( + [Parameter(Mandatory=$True, ValueFromPipelineByPropertyName = $True)] + [ValidateNotNullOrEmpty()] + [ValidatePattern('^http(s)\:\/\/.*\/.*\.(.{3,4})$')] + [System.URI]$URL, + + [Parameter(Mandatory=$False, ValueFromPipelineByPropertyName = $True)] + [ValidateNotNullOrEmpty()] + [System.IO.DirectoryInfo]$Destination, + + [Parameter(Mandatory=$False, ValueFromPipelineByPropertyName = $True)] + [ValidateNotNullOrEmpty()] + [ValidatePattern('^.*\.(.*)$')] + [String]$FileName, + + [Parameter(Mandatory=$False)] + [Switch]$ContinueOnError + ) + + Begin + { + Try + { + Switch ($True) + { + {([String]::IsNullOrEmpty($Destination) -eq $True) -or ([String]::IsNullOrWhiteSpace($Destination) -eq $True)} + { + [System.IO.DirectoryInfo]$Destination = "$($Env:Windir)\Temp" + } + + {([String]::IsNullOrEmpty($FileName) -eq $True) -or ([String]::IsNullOrWhiteSpace($FileName) -eq $True)} + { + [String]$FileName = [System.IO.Path]::GetFileName($URL.OriginalString) + } + } + + [System.IO.FileInfo]$DestinationPath = "$($Destination.FullName)\$($FileName)" + + $DateTimeLogFormat = 'dddd, MMMM dd, yyyy @ hh:mm:ss tt' ###Monday, January 01, 2019 @ 10:15:34 AM### + [ScriptBlock]$GetCurrentDateTimeLogFormat = {(Get-Date).ToString($DateTimeLogFormat)} + $DateTimeMessageFormat = 'MM/dd/yyyy HH:mm:ss.FFF' ###03/23/2022 11:12:48.347### + [ScriptBlock]$GetCurrentDateTimeMessageFormat = {(Get-Date).ToString($DateTimeMessageFormat)} + $DateFileFormat = 'yyyyMMdd' ###20190403### + [ScriptBlock]$GetCurrentDateFileFormat = {(Get-Date).ToString($DateFileFormat)} + $DateTimeFileFormat = 'yyyyMMdd_HHmmss' ###20190403_115354### + [ScriptBlock]$GetCurrentDateTimeFileFormat = {(Get-Date).ToString($DateTimeFileFormat)} + $TextInfo = (Get-Culture).TextInfo + $LoggingDetails = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary' + $LoggingDetails.Add('LogMessage', $Null) + $LoggingDetails.Add('WarningMessage', $Null) + $LoggingDetails.Add('ErrorMessage', $Null) + $CommonParameterList = New-Object -TypeName 'System.Collections.Generic.List[String]' + $CommonParameterList.AddRange([System.Management.Automation.PSCmdlet]::CommonParameters) + $CommonParameterList.AddRange([System.Management.Automation.PSCmdlet]::OptionalCommonParameters) + + [ScriptBlock]$ErrorHandlingDefinition = { + $ErrorMessageList = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary' + $ErrorMessageList.Add('Message', $_.Exception.Message) + $ErrorMessageList.Add('Category', $_.Exception.ErrorRecord.FullyQualifiedErrorID) + $ErrorMessageList.Add('Script', $_.InvocationInfo.ScriptName) + $ErrorMessageList.Add('LineNumber', $_.InvocationInfo.ScriptLineNumber) + $ErrorMessageList.Add('LinePosition', $_.InvocationInfo.OffsetInLine) + $ErrorMessageList.Add('Code', $_.InvocationInfo.Line.Trim()) + + ForEach ($ErrorMessage In $ErrorMessageList.GetEnumerator()) + { + $LoggingDetails.ErrorMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - ERROR: $($ErrorMessage.Key): $($ErrorMessage.Value)" + Write-Warning -Message ($LoggingDetails.ErrorMessage) + } + + Switch (($ContinueOnError.IsPresent -eq $False) -or ($ContinueOnError -eq $False)) + { + {($_ -eq $True)} + { + Throw + } + } + } + + #Determine the date and time we executed the function + $FunctionStartTime = (Get-Date) + + [String]$CmdletName = $MyInvocation.MyCommand.Name + + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Function `'$($CmdletName)`' is beginning. Please Wait..." + Write-Verbose -Message ($LoggingDetails.LogMessage) + + #Define Default Action Preferences + $ErrorActionPreference = 'Stop' + + [String[]]$AvailableScriptParameters = (Get-Command -Name ($CmdletName)).Parameters.GetEnumerator() | Where-Object {($_.Value.Name -inotin $CommonParameterList)} | ForEach-Object {"-$($_.Value.Name):$($_.Value.ParameterType.Name)"} + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Available Function Parameter(s) = $($AvailableScriptParameters -Join ', ')" + Write-Verbose -Message ($LoggingDetails.LogMessage) + + [String[]]$SuppliedScriptParameters = $PSBoundParameters.GetEnumerator() | ForEach-Object {"-$($_.Key):$($_.Value.GetType().Name)"} + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Supplied Function Parameter(s) = $($SuppliedScriptParameters -Join ', ')" + Write-Verbose -Message ($LoggingDetails.LogMessage) + + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Execution of $($CmdletName) began on $($FunctionStartTime.ToString($DateTimeLogFormat))" + Write-Verbose -Message ($LoggingDetails.LogMessage) + + $OutputObjectProperties = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary' + $OutputObjectProperties.DownloadRequired = $False + + #region Function Convert-FileSize + Function Convert-FileSize + { + <# + .SYSNOPSIS + Converts a size in bytes to its upper most value. + + .PARAMETER Size + The size in bytes to convert + + .EXAMPLE + $ConvertFileSizeParameters = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary' + $ConvertFileSizeParameters.Size = 4294964 + $ConvertFileSizeParameters.DecimalPlaces = 2 + + $ConvertFileSizeResult = Convert-FileSize @ConvertFileSizeParameters + + Write-Output -InputObject ($ConvertFileSizeResult) + + .EXAMPLE + $ConvertFileSizeResult = Convert-FileSize -Size 4294964 + + Write-Output -InputObject ($ConvertFileSizeResult) + + .NOTES + Size : 429496456565656 + DecimalPlaces : 0 + Divisor : 1099511627776 + SizeUnit : TB + SizeUnitAlias : Terabytes + CalculatedSize : 391 + CalculatedSizeStr : 391 TB + #> + + [CmdletBinding()] + Param + ( + [Parameter(Mandatory=$True)] + [ValidateNotNullOrEmpty()] + [Alias("Length")] + $Size, + + [Parameter(Mandatory=$False)] + [ValidateNotNullOrEmpty()] + [Alias("DP")] + [Int]$DecimalPlaces + ) + + Try + { + Switch ($True) + { + {([String]::IsNullOrEmpty($DecimalPlaces) -eq $True) -or ([String]::IsNullOrWhiteSpace($DecimalPlaces) -eq $True)} + { + [Int]$DecimalPlaces = 2 + } + } + + $OutputObjectProperties = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary' + $OutputObjectProperties.Size = $Size + $OutputObjectProperties.DecimalPlaces = $DecimalPlaces + + Switch ($Size) + { + {($_ -lt 1MB)} + { + $OutputObjectProperties.Divisor = 1KB + $OutputObjectProperties.SizeUnit = 'KB' + $OutputObjectProperties.SizeUnitAlias = 'Kilobytes' + + Break + } + + {($_ -lt 1GB)} + { + $OutputObjectProperties.Divisor = 1MB + $OutputObjectProperties.SizeUnit = 'MB' + $OutputObjectProperties.SizeUnitAlias = 'Megabytes' + + Break + } + + {($_ -lt 1TB)} + { + $OutputObjectProperties.Divisor = 1GB + $OutputObjectProperties.SizeUnit = 'GB' + $OutputObjectProperties.SizeUnitAlias = 'Gigabytes' + + Break + } + + {($_ -ge 1TB)} + { + $OutputObjectProperties.Divisor = 1TB + $OutputObjectProperties.SizeUnit = 'TB' + $OutputObjectProperties.SizeUnitAlias = 'Terabytes' + + Break + } + } + + $OutputObjectProperties.CalculatedSize = [System.Math]::Round(($Size / $OutputObjectProperties.Divisor), $OutputObjectProperties.DecimalPlaces) + $OutputObjectProperties.CalculatedSizeStr = "$($OutputObjectProperties.CalculatedSize) $($OutputObjectProperties.SizeUnit)" + } + Catch + { + Write-Error -Exception $_ + } + Finally + { + $OutputObject = New-Object -TypeName 'PSObject' -Property ($OutputObjectProperties) + + Write-Output -InputObject ($OutputObject) + } + } + #endregion + } + Catch + { + $ErrorHandlingDefinition.Invoke() + } + Finally + { + + } + } + + Process + { + Try + { + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Attempting to create a web request for `"$($URL.OriginalString)`". Please Wait..." + Write-Verbose -Message ($LoggingDetails.LogMessage) + + $WebRequest = [System.Net.WebRequest]::Create($URL.OriginalString) + + $WebRequestResponse = $WebRequest.GetResponse() + + $WebRequestResponseHeaders = $WebRequestResponse.Headers + + $WebRequestHeaderProperties = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary' + + ForEach ($WebRequestResponseHeader In $WebRequestResponseHeaders.AllKeys) + { + $WebRequestHeaderProperties."$($WebRequestResponseHeader)" = ($WebRequestResponseHeaders.GetValues($WebRequestResponseHeader))[0] + } + + $WebRequestHeaders = New-Object -TypeName 'PSObject' -Property ($WebRequestHeaderProperties) + + $WebRequestHeaders.'Last-Modified' = (Get-Date -Date $WebRequestHeaders.'Last-Modified').ToUniversalTime() + + $ContentLengthInMB = [System.Math]::Round(($WebRequestHeaders.'Content-Length' / 1MB), 2) + + [ScriptBlock]$ExecuteDownload = { + Try + { + $DownloadExecutionStopwatch = New-Object -TypeName 'System.Diagnostics.Stopwatch' + + $WebClient = New-Object -TypeName 'System.Net.WebClient' + $WebClient.UseDefaultCredentials = $True + + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Attempting to download URL `"$($URL.OriginalString)`" to `"$($DestinationPath.FullName)`". Please Wait..." + Write-Verbose -Message ($LoggingDetails.LogMessage) + + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Download Size: $($ContentLengthInMB) MB" + Write-Verbose -Message ($LoggingDetails.LogMessage) + + If ([System.IO.Directory]::Exists($DestinationPath.Directory.FullName) -eq $False) {$Null = [System.IO.Directory]::CreateDirectory($DestinationPath.Directory.FullName)} + + $Downloader = $WebClient.DownloadFileTaskAsync($URL.OriginalString, $DestinationPath.FullName) + + $Null = Register-ObjectEvent -InputObject ($WebClient) -EventName 'DownloadProgressChanged' -SourceIdentifier 'WebClient.DownloadProgressChanged' + + $Null = Start-Sleep -Seconds 3 + + $Null = $DownloadExecutionStopwatch.Start() + + Switch ($Downloader.IsFaulted) + { + {($_ -eq $True)} + { + $LoggingDetails.WarningMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - A download error has occured. Attempting to generate the error record. Please Wait..." + Write-Warning -Message ($LoggingDetails.WarningMessage) + + Write-Error -Message ($Downloader.GetAwaiter().GetResult()) + } + } + + While ($Downloader.IsCompleted -eq $False) + { + Switch ($Downloader.IsFaulted) + { + {($_ -eq $True)} + { + $LoggingDetails.WarningMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - A download error has occured. Attempting to generate the error record. Please Wait..." + Write-Warning -Message ($LoggingDetails.WarningMessage) + + Write-Error -Message ($Downloader.GetAwaiter().GetResult()) + + Break + } + } + + $EventData = Get-Event -SourceIdentifier 'WebClient.DownloadProgressChanged' | Select-Object -ExpandProperty 'SourceEventArgs' -Last 1 + + $EventDetails = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary' + $EventDetails.Received = Convert-FileSize -Size ($EventData.BytesReceived) -DecimalPlaces 2 + $EventDetails.TotalToReceive = Convert-FileSize -Size ($EventData.TotalBytesToReceive) -DecimalPlaces 2 + $EventDetails.ProgressPercentage = $EventData.ProgressPercentage + + Switch ($DownloadExecutionStopwatch.Elapsed.Seconds -gt 0) + { + {($_ -eq $True)} + { + [Single]$TransferRate = [System.Math]::Round(($EventDetails.TotalToReceive.Size / $DownloadExecutionStopwatch.Elapsed.Seconds / 1MB), 2) + } + + Default + { + [Single]$TransferRate = 0.00 + } + } + + $WriteProgressParameters = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary' + $WriteProgressParameters.Activity = "Downloading `"$($DestinationPath.Name)`"" + $WriteProgressParameters.Status = "Completion Percentage: $($EventDetails.ProgressPercentage)%" + $WriteProgressParameters.PercentComplete = $EventDetails.ProgressPercentage + $WriteProgressParameters.CurrentOperation = "Downloaded $($EventDetails.Received.CalculatedSizeStr) of $($EventDetails.TotalToReceive.CalculatedSizeStr) @ $($TransferRate) Mbps" + + Write-Progress @WriteProgressParameters + } + } + Catch + { + Write-Error -Exception $_ + + $Null = Unregister-Event -SourceIdentifier 'WebClient.DownloadProgressChanged' + + $Null = $DownloadExecutionStopwatch.Stop() + + $Null = $DownloadExecutionStopwatch.Reset() + + Break + } + Finally + { + $WriteProgressParameters = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary' + $WriteProgressParameters.Activity = "Downloading `"$($DestinationPath.Name)`"" + $WriteProgressParameters.Completed = $True + + Write-Progress @WriteProgressParameters + + $Null = Unregister-Event -SourceIdentifier 'WebClient.DownloadProgressChanged' + + Switch (($Downloader.IsCompleted -eq $False) -or ($Downloader.IsFaulted -eq $True)) + { + {($_ -eq $True)} + { + $Null = $Downloader.CancelAsync() + + $LoggingDetails.WarningMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Download failed. Attempting to remove incomplete file `"$($DestinationPath.FullName)`". Please Wait..." + Write-Warning -Message ($LoggingDetails.WarningMessage) + + $Null = [System.IO.File]::Delete($DestinationPath.FullName) + } + } + + $Null = $WebClient.Dispose() + + $Null = $DownloadExecutionStopwatch.Stop() + } + + $DownloadExecutionTimespan = $DownloadExecutionStopwatch.Elapsed + + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Download completed in $($DownloadExecutionTimespan.Hours.ToString()) hour(s), $($DownloadExecutionTimespan.Minutes.ToString()) minute(s), $($DownloadExecutionTimespan.Seconds.ToString()) second(s), and $($DownloadExecutionTimespan.Milliseconds.ToString()) millisecond(s)." + Write-Verbose -Message ($LoggingDetails.LogMessage) + + $Null = $DownloadExecutionStopwatch.Reset() + + [Int]$SecondsToWait = 3 + + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Pausing script execution for $($SecondsToWait) second(s). Please Wait..." + Write-Verbose -Message ($LoggingDetails.LogMessage) + + $Null = Start-Sleep -Seconds ($SecondsToWait) + + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Attempting to update the last modified date of `"$($DestinationPath.FullName)`" to match the last modified date of `"$($URL.OriginalString)`". Please Wait..." + Write-Verbose -Message ($LoggingDetails.LogMessage) + + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Last Modified Date UTC (Local File): $($DestinationPath.LastWriteTimeUTC.ToString($DateTimeLogFormat))" + Write-Verbose -Message ($LoggingDetails.LogMessage) + + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Last Modified Date UTC (URL Header): $($WebRequestHeaders.'Last-Modified'.ToString($DateTimeLogFormat))" + Write-Verbose -Message ($LoggingDetails.LogMessage) + + $DestinationPathDetails = Get-Item -Path $DestinationPath.FullName -Force + + $Null = $DestinationPathDetails.LastWriteTimeUTC = $WebRequestHeaders.'Last-Modified' + } + + Switch ([System.IO.File]::Exists($DestinationPath.FullName)) + { + {($_ -eq $True)} + { + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Destination path `"$($DestinationPath.FullName)`" already exists." + Write-Verbose -Message ($LoggingDetails.LogMessage) + + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Attempting to check the last modified date of `"$($DestinationPath.FullName)`" to see if a download is necessary." + Write-Verbose -Message ($LoggingDetails.LogMessage) + + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Last Modified Date UTC (Local File): $($DestinationPath.LastWriteTimeUTC.ToString($DateTimeLogFormat))" + Write-Verbose -Message ($LoggingDetails.LogMessage) + + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Last Modified Date UTC (URL Header): $($WebRequestHeaders.'Last-Modified'.ToString($DateTimeLogFormat))" + Write-Verbose -Message ($LoggingDetails.LogMessage) + + Switch (($DestinationPath.LastWriteTimeUTC -ine $WebRequestHeaders.'Last-Modified')) + { + {($_ -eq $True)} + { + $LoggingDetails.WarningMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - A redownload of `"$($URL.OriginalString)`" is necessary." + Write-Warning -Message ($LoggingDetails.WarningMessage) + + $OutputObjectProperties.DownloadRequired = $True + + $ExecuteDownload.InvokeReturnAsIs() + } + + Default + { + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - A redownload of `"$($URL.OriginalString)`" is not necessary." + Write-Verbose -Message ($LoggingDetails.LogMessage) + } + } + } + + Default + { + $LoggingDetails.WarningMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Destination path `"$($DestinationPath.FullName)`" does not exist." + Write-Warning -Message ($LoggingDetails.WarningMessage) + + $LoggingDetails.WarningMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - A download of `"$($URL.OriginalString)`" is necessary." + Write-Warning -Message ($LoggingDetails.WarningMessage) + + $OutputObjectProperties.DownloadRequired = $True + + $ExecuteDownload.InvokeReturnAsIs() + } + } + } + Catch + { + $ErrorHandlingDefinition.Invoke() + } + Finally + { + Try {$Null = $WebRequestResponse.Dispose()} Catch {} + } + } + + End + { + Try + { + #Determine the date and time the function completed execution + $FunctionEndTime = (Get-Date) + + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Execution of $($CmdletName) ended on $($FunctionEndTime.ToString($DateTimeLogFormat))" + Write-Verbose -Message ($LoggingDetails.LogMessage) + + #Log the total script execution time + $FunctionExecutionTimespan = New-TimeSpan -Start ($FunctionStartTime) -End ($FunctionEndTime) + + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Function execution took $($FunctionExecutionTimespan.Hours.ToString()) hour(s), $($FunctionExecutionTimespan.Minutes.ToString()) minute(s), $($FunctionExecutionTimespan.Seconds.ToString()) second(s), and $($FunctionExecutionTimespan.Milliseconds.ToString()) millisecond(s)." + Write-Verbose -Message ($LoggingDetails.LogMessage) + + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Function `'$($CmdletName)`' is completed." + Write-Verbose -Message ($LoggingDetails.LogMessage) + } + Catch + { + $ErrorHandlingDefinition.Invoke() + } + Finally + { + $DestinationPathDetails = Get-Item -Path $DestinationPath.FullName -Force + + $OutputObjectProperties.DownloadPath = $DestinationPathDetails + $OutputObjectProperties.URL = $URL + $OutputObjectProperties.URLHeaders = $WebRequestHeaders + + $OutputObject = New-Object -TypeName 'PSObject' -Property ($OutputObjectProperties) + + Write-Output -InputObject ($OutputObject) + } + } + } +#endregion \ No newline at end of file diff --git a/Functions/Start-ProcessWithOutput.ps1 b/Functions/Start-ProcessWithOutput.ps1 new file mode 100644 index 0000000..1279f3b --- /dev/null +++ b/Functions/Start-ProcessWithOutput.ps1 @@ -0,0 +1,443 @@ +#region Start-ProcessWithOutput +Function Start-ProcessWithOutput + { + <# + .SYNOPSIS + Allows for the execution of processes with the ability to return their output without first dumping the content to a file. It can all be kept in memory. + + .DESCRIPTION + This is not the best option when your process returns a large enough amount of output to cause a memory leak or overflow. + + .PARAMETER FilePath + Your parameter description + + .PARAMETER WorkingDirectory + Your parameter description + + .PARAMETER ArgumentList + Your parameter description + + .PARAMETER AcceptableExitCodeList + A * can be used to accept all exit codes. + + .PARAMETER WindowStyle + Your parameter description + + .PARAMETER CreateNoWindow + Your parameter description + + .PARAMETER ParseOutput + Enables the parsing of the command output into objects. + + .PARAMETER ParsingExpression + A valid regular expression that will allow for the output to be parsed into objects. + + .PARAMETER LogOutput + Your parameter description + + .EXAMPLE + Start-ProcessWithOutput -FilePath 'cmd.exe' -ArgumentList '/c ipconfig /all' -AcceptableExitCodeList @('*') -CreateNoWindow -WindowStyle 'Hidden' -LogOutput -Verbose + + .EXAMPLE + $StartProcessWithOutputParameters = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary' + $StartProcessWithOutputParameters.FilePath = "dsregcmd.exe" + $StartProcessWithOutputParameters.WorkingDirectory = "$([System.Environment]::SystemDirectory)" + $StartProcessWithOutputParameters.ArgumentList = New-Object -TypeName 'System.Collections.Generic.List[String]' + $StartProcessWithOutputParameters.ArgumentList.Add('/status') + $StartProcessWithOutputParameters.AcceptableExitCodeList = New-Object -TypeName 'System.Collections.Generic.List[String]' + $StartProcessWithOutputParameters.AcceptableExitCodeList.Add('0') + $StartProcessWithOutputParameters.WindowStyle = "Hidden" + $StartProcessWithOutputParameters.CreateNoWindow = $True + $StartProcessWithOutputParameters.ParseOutput = $True + $StartProcessWithOutputParameters.ParsingExpression = "(?:\s+)(?.+)(?:\s+\:\s+)(?.+)" + $StartProcessWithOutputParameters.LogOutput = $True + $StartProcessWithOutputParameters.Verbose = $True + + $StartProcessWithOutputResult = Start-ProcessWithOutput @StartProcessWithOutputParameters + + Write-Output -InputObject ($StartProcessWithOutputResult) + + .NOTES + Mileage may vary when parsing output and may have to be done using additional code outside of this function to address specific needs. + + .LINK + Place any useful link here where your function or cmdlet can be referenced + #> + + [CmdletBinding()] + Param + ( + [Parameter(Mandatory=$True)] + [ValidateNotNullOrEmpty()] + [String]$FilePath, + + [Parameter(Mandatory=$False)] + [ValidateNotNullOrEmpty()] + [String]$WorkingDirectory, + + [Parameter(Mandatory=$False)] + [AllowEmptyCollection()] + [AllowNull()] + [String[]]$ArgumentList, + + [Parameter(Mandatory=$False)] + [AllowEmptyCollection()] + [AllowNull()] + [String[]]$AcceptableExitCodeList, + + [Parameter(Mandatory=$False)] + [ValidateNotNullOrEmpty()] + [ValidateSet('Normal', 'Hidden', 'Minimized', 'Maximized')] + [String]$WindowStyle, + + [Parameter(Mandatory=$False)] + [Switch]$CreateNoWindow, + + [Parameter(Mandatory=$False, ParameterSetName = 'ParseOutput')] + [Switch]$ParseOutput, + + [Parameter(Mandatory=$False, ParameterSetName = 'ParseOutput')] + [ValidateNotNullOrEmpty()] + [Alias('StandardOutputParsingExpression')] + [Regex]$ParsingExpression, + + [Parameter(Mandatory=$False)] + [Switch]$LogOutput + ) + + Try + { + $DateTimeLogFormat = 'dddd, MMMM dd, yyyy @ hh:mm:ss.FFF tt' ###Monday, January 01, 2019 @ 10:15:34.000 AM### + [ScriptBlock]$GetCurrentDateTimeLogFormat = {(Get-Date).ToString($DateTimeLogFormat)} + + $DateTimeMessageFormat = 'MM/dd/yyyy HH:mm:ss.FFF' ###03/23/2022 11:12:48.347### + [ScriptBlock]$GetCurrentDateTimeMessageFormat = {(Get-Date).ToString($DateTimeMessageFormat)} + + $DateFileFormat = 'yyyyMMdd' ###20190403### + [ScriptBlock]$GetCurrentDateFileFormat = {(Get-Date).ToString($DateFileFormat)} + + $DateTimeFileFormat = 'yyyyMMdd_HHmmss' ###20190403_115354### + [ScriptBlock]$GetCurrentDateTimeFileFormat = {(Get-Date).ToString($DateTimeFileFormat)} + + $LoggingDetails = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary' + $LoggingDetails.LogMessage = $Null + $LoggingDetails.WarningMessage = $Null + $LoggingDetails.ErrorMessage = $Null + + #Determine the date and time we executed the function + $FunctionStartTime = (Get-Date) + + [String]$CmdletName = $MyInvocation.MyCommand.Name + + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Function `'$($CmdletName)`' is beginning. Please Wait..." + Write-Verbose -Message ($LoggingDetails.LogMessage) + + #Define Default Action Preferences + $ErrorActionPreference = 'Stop' + + [String[]]$AvailableScriptParameters = (Get-Command -Name ($CmdletName)).Parameters.GetEnumerator() | Where-Object {($_.Value.Name -inotin $CommonParameterList)} | ForEach-Object {"-$($_.Value.Name):$($_.Value.ParameterType.Name)"} + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Available Function Parameter(s) = $($AvailableScriptParameters -Join ', ')" + Write-Verbose -Message ($LoggingDetails.LogMessage) + + [String[]]$SuppliedScriptParameters = $PSBoundParameters.GetEnumerator() | ForEach-Object {"-$($_.Key):$($_.Value.GetType().Name)"} + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Supplied Function Parameter(s) = $($SuppliedScriptParameters -Join ', ')" + Write-Verbose -Message ($LoggingDetails.LogMessage) + + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Execution of $($CmdletName) began on $($FunctionStartTime.ToString($DateTimeLogFormat))" + Write-Verbose -Message ($LoggingDetails.LogMessage) + + #Set default parameter values (If necessary) + Switch ($True) + { + {([String]::IsNullOrEmpty($WindowStyle) -eq $True) -or ([String]::IsNullOrWhiteSpace($WindowStyle) -eq $True)} + { + [String]$WindowStyle = 'Hidden' + } + + {([String]::IsNullOrEmpty($ParsingExpression) -eq $True) -or ([String]::IsNullOrWhiteSpace($ParsingExpression) -eq $True)} + { + [Regex]$ParsingExpression = '(?:\s+)(?.+)(?:\s+\:\s+)(?.+)' + } + } + + $OutputObjectProperties = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary' + $OutputObjectProperties.ExitCode = $Null + $OutputObjectProperties.ExitCodeAsHex = $Null + $OutputObjectProperties.ExitCodeAsInteger = $Null + $OutputObjectProperties.ExitCodeAsDecimal = $Null + $OutputObjectProperties.ProcessObject = $Null + $OutputObjectProperties.StandardOutput = $Null + $OutputObjectProperties.StandardOutputObject = $Null + $OutputObjectProperties.StandardError = $Null + $OutputObjectProperties.StandardErrorObject = $Null + + $Process = New-Object -TypeName 'System.Diagnostics.Process' + $Process.StartInfo.FileName = $FilePath + $Process.StartInfo.UseShellExecute = $False + $Process.StartInfo.RedirectStandardOutput = $True + $Process.StartInfo.RedirectStandardError = $True + + Switch ($True) + { + {([String]::IsNullOrEmpty($WorkingDirectory) -eq $False) -and ([String]::IsNullOrWhiteSpace($WorkingDirectory) -eq $False)} + { + $Process.StartInfo.WorkingDirectory = $WorkingDirectory + } + } + + Switch ($CreateNoWindow.IsPresent) + { + {($_ -eq $True)} + { + $Process.StartInfo.CreateNoWindow = ($CreateNoWindow.IsPresent) + } + + Default + { + $Process.StartInfo.WindowStyle = [System.Diagnostics.ProcessWindowStyle]::$($WindowStyle) + } + } + + Switch (($Null -ieq $AcceptableExitCodeList) -or ($AcceptableExitCodeList.Count -eq 0)) + { + {($_ -eq $True)} + { + $AcceptableExitCodeList = @() + + $AcceptableExitCodeList += '0' + $AcceptableExitCodeList += '3010' + } + } + + Switch (($Null -ine $ArgumentList) -and ($ArgumentList.Count -gt 0)) + { + {($_ -eq $True)} + { + $Process.StartInfo.Arguments = $ArgumentList -Join ' ' + + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Attempting to execute the following command: `"$($Process.StartInfo.FileName)`" $($Process.StartInfo.Arguments)" + Write-Verbose -Message ($LoggingDetails.LogMessage) + } + + Default + { + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Attempting to execute the following command: `"$($Process.StartInfo.FileName)`"" + Write-Verbose -Message ($LoggingDetails.LogMessage) + } + } + + $Null = $Process.Start() + + $OutputObjectProperties.StandardOutput = $Process.StandardOutput.ReadToEnd() + $OutputObjectProperties.StandardError = $Process.StandardError.ReadToEnd() + + $Null = $Process.WaitForExit() + + $OutputObjectProperties.ExitCode = $Process.ExitCode + $OutputObjectProperties.ExitCodeAsHex = Try {'0x' + [System.Convert]::ToString($OutputObjectProperties.ExitCode, 16).PadLeft(8, '0').ToUpper()} Catch {$Null} + $OutputObjectProperties.ExitCodeAsInteger = Try {$OutputObjectProperties.ExitCodeAsHex -As [Int]} Catch {$Null} + $OutputObjectProperties.ExitCodeAsDecimal = Try {[System.Convert]::ToString($OutputObjectProperties.ExitCodeAsHex, 10)} Catch {$Null} + + $ExitCodeMessageList = New-Object -TypeName 'System.Collections.Generic.List[String]' + + $Null = $OutputObjectProperties.GetEnumerator() | Where-Object {($_.Key -imatch '(^ExitCode.*$)')} | Sort-Object -Property @('Key') | ForEach-Object {$ExitCodeMessageList.Add("[$($_.Key): $($_.Value)]")} + + $StartProcessExecutionTimespan = New-TimeSpan -Start ($Process.StartTime) -End ($Process.ExitTime) + + $OutputObjectProperties.ProcessObject = $Process + + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - The command execution took $($StartProcessExecutionTimespan.Hours.ToString()) hour(s), $($StartProcessExecutionTimespan.Minutes.ToString()) minute(s), $($StartProcessExecutionTimespan.Seconds.ToString()) second(s), and $($StartProcessExecutionTimespan.Milliseconds.ToString()) millisecond(s)." + Write-Verbose -Message ($LoggingDetails.LogMessage) + + Switch (($AcceptableExitCodeList -icontains '*') -or ($OutputObjectProperties.ExitCode.ToString() -iin $AcceptableExitCodeList) -or ($OutputObjectProperties.ExitCodeAsHex.ToString() -iin $AcceptableExitCodeList) -or ($OutputObjectProperties.ExitCodeAsInteger.ToString() -iin $AcceptableExitCodeList) -or ($OutputObjectProperties.ExitCodeAsDecimal.ToString() -iin $AcceptableExitCodeList)) + { + {($_ -eq $True)} + { + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - The command execution was successful. $($ExitCodeMessageList -Join ' ')" + Write-Verbose -Message ($LoggingDetails.LogMessage) + } + + {($_ -eq $False)} + { + $LoggingDetails.WarningMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - The command execution was unsuccessful. $($ExitCodeMessageList -Join ' ')" + Write-Warning -Message ($LoggingDetails.WarningMessage) -Verbose + + $ErrorMessage = "$($LoggingDetails.WarningMessage)" + $Exception = [System.Exception]::New($ErrorMessage) + $ErrorRecord = [System.Management.Automation.ErrorRecord]::New($Exception, [System.Management.Automation.ErrorCategory]::InvalidResult.ToString(), [System.Management.Automation.ErrorCategory]::InvalidResult, $Process) + + $PSCmdlet.ThrowTerminatingError($ErrorRecord) + } + } + + Switch (($OutputObjectProperties.ExitCode.ToString() -iin $AcceptableExitCodeList) -or ($OutputObjectProperties.ExitCodeAsHex.ToString() -iin $AcceptableExitCodeList) -or ($OutputObjectProperties.ExitCodeAsInteger.ToString() -iin $AcceptableExitCodeList) -or ($OutputObjectProperties.ExitCodeAsDecimal.ToString() -iin $AcceptableExitCodeList)) + { + {($_ -eq $True)} + { + [String]$CommandContents = $OutputObjectProperties.StandardOutput + + Switch (($ParseOutput.IsPresent -eq $True) -and ([String]::IsNullOrEmpty($ParsingExpression) -eq $False) -and ([String]::IsNullOrWhiteSpace($ParsingExpression) -eq $False)) + { + {($_ -eq $True)} + { + $RegexOptions = New-Object -TypeName 'System.Collections.Generic.List[System.Text.RegularExpressions.RegexOptions]' + $RegexOptions.Add([System.Text.RegularExpressions.RegexOptions]::IgnoreCase) + $RegexOptions.Add([System.Text.RegularExpressions.RegexOptions]::Multiline) + + [System.Text.RegularExpressions.Regex]$RegularExpression = [System.Text.RegularExpressions.Regex]::New($ParsingExpression, $RegexOptions.ToArray()) + + [String[]]$RegularExpressionGroups = $RegularExpression.GetGroupNames() | Where-Object {($_ -notin @('0'))} + + [System.Text.RegularExpressions.MatchCollection]$RegularExpressionMatches = $RegularExpression.Matches($CommandContents) + + $StandardOutputObjectProperties = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary' + + For ($RegularExpressionMatchIndex = 0; $RegularExpressionMatchIndex -lt $RegularExpressionMatches.Count; $RegularExpressionMatchIndex++) + { + [System.Text.RegularExpressions.Match]$RegularExpressionMatch = $RegularExpressionMatches[$RegularExpressionMatchIndex] + + For ($RegularExpressionGroupIndex = 0; $RegularExpressionGroupIndex -lt $RegularExpressionGroups.Count; $RegularExpressionGroupIndex++) + { + [String]$RegularExpressionGroup = $RegularExpressionGroups[$RegularExpressionGroupIndex] + + Switch ($RegularExpressionGroup) + { + {($_ -imatch '(^PropertyName$)')} + { + $PropertyDetails = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary' + $PropertyDetails.Add('Name', $Null) + $PropertyDetails.Add('Value', $Null) + + $PropertyDetails.Name = ($RegularExpressionMatch.Groups[$($RegularExpressionGroup)].Value) -ireplace '(\s+)|(\-)|(_)', '' + } + + {($_ -imatch '(^PropertyValue$)')} + { + $PropertyDetails.Value = $RegularExpressionMatch.Groups[$($RegularExpressionGroup)].Value + + Switch ($True) + { + {($PropertyDetails.Value -imatch '\+(\-){1,}\+')} + { + $PropertyDetails.Value = $Null + } + + {($PropertyDetails.Value -imatch '(.+\,\s+.+){1,}')} + { + #$PropertyDetails.Value = $PropertyDetails.Value.Split(',').Trim() + } + + {($PropertyDetails.Value -imatch '.+\(.+\).+')} + { + #$PropertyDetails.Value = ($PropertyDetails.Value.Split('()', [System.StringSplitOptions]::RemoveEmptyEntries) -ireplace 'bytes', '')[1] + } + } + + Switch ($Null -ine $PropertyDetails.Value) + { + {($_ -eq $True)} + { + $PropertyDetails.Value = $PropertyDetails.Value.Trim() + } + } + } + } + } + + Switch ($StandardOutputObjectProperties.Contains($PropertyDetails.Name)) + { + {($_ -eq $False)} + { + $Null = $StandardOutputObjectProperties.Add($PropertyDetails.Name, $PropertyDetails.Value) + } + } + } + + $OutputObjectProperties.StandardOutputObject = New-Object -TypeName 'PSObject' -Property ($StandardOutputObjectProperties) + } + } + } + } + } + Catch + { + $ErrorMessageList = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary' + $ErrorMessageList.Add('Message', $_.Exception.Message) + $ErrorMessageList.Add('Category', $_.Exception.ErrorRecord.FullyQualifiedErrorID) + $ErrorMessageList.Add('Script', $_.InvocationInfo.ScriptName) + $ErrorMessageList.Add('LineNumber', $_.InvocationInfo.ScriptLineNumber) + $ErrorMessageList.Add('LinePosition', $_.InvocationInfo.OffsetInLine) + $ErrorMessageList.Add('Code', $_.InvocationInfo.Line.Trim()) + + ForEach ($ErrorMessage In $ErrorMessageList.GetEnumerator()) + { + $LoggingDetails.ErrorMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - ERROR: $($ErrorMessage.Key): $($ErrorMessage.Value)" + Write-Warning -Message ($LoggingDetails.ErrorMessage) -Verbose + } + + Throw "$($_.Exception.Message)" + } + Finally + { + #Dispose of the process object + Try {$Null = $Process.Dispose()} Catch {} + + #Determine the date and time the function completed execution + $FunctionEndTime = (Get-Date) + + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Execution of $($CmdletName) ended on $($FunctionEndTime.ToString($DateTimeLogFormat))" + Write-Verbose -Message ($LoggingDetails.LogMessage) + + #Log the total script execution time + $FunctionExecutionTimespan = New-TimeSpan -Start ($FunctionStartTime) -End ($FunctionEndTime) + + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Function execution took $($FunctionExecutionTimespan.Hours.ToString()) hour(s), $($FunctionExecutionTimespan.Minutes.ToString()) minute(s), $($FunctionExecutionTimespan.Seconds.ToString()) second(s), and $($FunctionExecutionTimespan.Milliseconds.ToString()) millisecond(s)." + Write-Verbose -Message ($LoggingDetails.LogMessage) + + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Function `'$($CmdletName)`' is completed." + Write-Verbose -Message ($LoggingDetails.LogMessage) + + $OutputObject = New-Object -TypeName 'PSObject' -Property ($OutputObjectProperties) + + Switch (($LogOutput.IsPresent -eq $True) -or ($LogOutput -eq $True)) + { + {($_ -eq $True)} + { + ForEach ($Property In $OutputObject.PSObject.Properties) + { + Switch ($Property.Name) + { + {($_ -iin @('StandardOutput', 'StandardError'))} + { + Switch (([String]::IsNullOrEmpty($Property.Value) -eq $False) -and ([String]::IsNullOrWhiteSpace($Property.Value) -eq $False)) + { + {($_ -eq $True)} + { + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - $($Property.Name): $($Property.Value)" + Write-Verbose -Message ($LoggingDetails.LogMessage) -Verbose + } + + Default + { + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - $($Property.Name): N/A" + Write-Verbose -Message ($LoggingDetails.LogMessage) -Verbose + } + } + } + } + } + } + } + + Write-Output -InputObject ($OutputObject) + } + } +#endregion + +<# + $ProcessOutput = Start-ProcessWithOutput -FilePath 'dsregcmd.exe' -ArgumentList '/status' -CreateNoWindow -Verbose + + $ProcessOutput.StandardOutputObject | ConvertTo-JSON -Depth 10 -OutVariable 'AzureADDetails' + + $ProcessOutput +#> \ No newline at end of file diff --git a/Guides/Place_Additional_Guides_Here.txt b/Guides/Place_Additional_Guides_Here.txt deleted file mode 100644 index fa7cc9c..0000000 --- a/Guides/Place_Additional_Guides_Here.txt +++ /dev/null @@ -1 +0,0 @@ -### \ No newline at end of file diff --git a/Functions/Place_Additional_Functions_Here.txt b/Guides/Place_Guides_Here.txt similarity index 100% rename from Functions/Place_Additional_Functions_Here.txt rename to Guides/Place_Guides_Here.txt diff --git a/Modules/Place_Additional_Modules_Here.txt b/Modules/Place_Additional_Modules_Here.txt deleted file mode 100644 index fa7cc9c..0000000 --- a/Modules/Place_Additional_Modules_Here.txt +++ /dev/null @@ -1 +0,0 @@ -### \ No newline at end of file diff --git a/OSD-ScriptTemplate.ps1 b/OSD-ScriptTemplate.ps1 index 65a3335..1ceb0f9 100644 --- a/OSD-ScriptTemplate.ps1 +++ b/OSD-ScriptTemplate.ps1 @@ -1,237 +1,865 @@ -#Requires -Version 3 - -<# - .SYNOPSIS - A brief overview of what your function does - - .DESCRIPTION - Slightly more detailed description of what your function does - - .PARAMETER ParameterName - Your parameter description - - .PARAMETER ParameterName - Your parameter description - - .PARAMETER ParameterName - Your parameter description - - .PARAMETER LogDir - A valid folder path. If the folder does not exist, it will be created. This parameter can also be specified by the alias "LogPath". - - .PARAMETER ContinueOnError - Ignore failures. - - .EXAMPLE - powershell.exe -ExecutionPolicy Bypass -NoProfile -NoLogo -File "%FolderPathContainingScript%\%ScriptName%.ps1" - - .EXAMPLE - powershell.exe -ExecutionPolicy Bypass -NoProfile -NoLogo -File "%FolderPathContainingScript%\%ScriptName%.ps1" -ScriptParameter "%ScriptParameterValue%" - - .EXAMPLE - powershell.exe -ExecutionPolicy Bypass -NoProfile -NoLogo -File "%FolderPathContainingScript%\%ScriptName%.ps1" -SwitchParameter - - .NOTES - Any useful tidbits - - .LINK - Place any useful link here where your function or cmdlet can be referenced +#Requires -Version 3 + +<# + .SYNOPSIS + A brief overview of what your function does + + .DESCRIPTION + Slightly more detailed description of what your function does + + .PARAMETER TaskSequenceVariables + One or more task sequence variable(s) to retrieve during task sequence execution. + If this parameter is not specified, all task sequence variable(s) will be stored into the variable 'TSVariableTable'. + Any task sequence variables that are new or have been updated will be saved back to the task sequence engine for futher usage. + + $TSVariable.MyCustomVariableName = "MyCustomVariableValue" + $TSVariable.Make = "MyDeviceModel" + + .PARAMETER LogDir + A valid folder path. If the folder does not exist, it will be created. This parameter can also be specified by the alias "LogPath". + + .PARAMETER ContinueOnError + Ignore failures. + + .EXAMPLE + Use this command to execute a VBSCript that will launch this powershell script automatically with the specified parameters. This is useful to avoid powershell execution complexities. + + cscript.exe /nologo "%FolderPathContainingScript%\%ScriptName%.vbs" /SwitchParameter /ScriptParameter:"%ScriptParameterValue%" /ScriptParameterArray:"%ScriptParameterValue1%,%ScriptParameterValue2%" + + wscript.exe /nologo "%FolderPathContainingScript%\%ScriptName%.vbs" /SwitchParameter /ScriptParameter:"%ScriptParameterValue%" /ScriptParameterArray:"%ScriptParameterValue1%,%ScriptParameterValue2%" + + .EXAMPLE + powershell.exe -ExecutionPolicy Bypass -NoProfile -NoLogo -File "%FolderPathContainingScript%\%ScriptName%.ps1" -SwitchParameter -ScriptParameter "%ScriptParameterValue%" + + .EXAMPLE + powershell.exe -ExecutionPolicy Bypass -NonInteractive -NoProfile -NoLogo -WindowStyle Hidden -Command "& '%FolderPathContainingScript%\%ScriptName%.ps1' -ScriptParameter1 '%ScriptParameter1Value%' -ScriptParameter2 %ScriptParameter2Value% -SwitchParameter" + + .NOTES + Any useful tidbits + + .LINK + Place any useful link here where your function or cmdlet can be referenced #> -[CmdletBinding()] - Param - ( - [Parameter(Mandatory=$False)] - [ValidateNotNullOrEmpty()] - [ValidateScript({($_ -imatch '^[a-zA-Z][\:]\\{1,}$') -or ($_ -imatch '^[a-zA-Z][\:]\\.*?[^\\]$')})] - [System.IO.DirectoryInfo]$ImagePath, +[CmdletBinding(SupportsShouldProcess=$True)] + Param + ( + [Parameter(Mandatory=$False)] + [ValidateNotNullOrEmpty()] + [Alias('TSVars', 'TSVs')] + [String[]]$TaskSequenceVariables, - [Parameter(Mandatory=$False)] - [ValidateNotNullOrEmpty()] - [ValidateScript({($_ -imatch '^[a-zA-Z][\:]\\.*?[^\\]$')})] - [Alias('LogPath')] - [System.IO.DirectoryInfo]$LogDir = "$($Env:Windir)\Logs\Software", + [Parameter(Mandatory=$False)] + [ValidateNotNullOrEmpty()] + [Alias('LogDir', 'LogPath')] + [System.IO.DirectoryInfo]$LogDirectory, - [Parameter(Mandatory=$False)] - [Switch]$ContinueOnError - ) + [Parameter(Mandatory=$False)] + [Switch]$ContinueOnError + ) + +Function Get-AdministrativePrivilege + { + $Identity = [System.Security.Principal.WindowsIdentity]::GetCurrent() + $Principal = New-Object System.Security.Principal.WindowsPrincipal($Identity) + Write-Output -InputObject ($Principal.IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator)) + } -#Define Default Action Preferences - $Script:DebugPreference = 'SilentlyContinue' - $Script:ErrorActionPreference = 'Stop' - $Script:VerbosePreference = 'SilentlyContinue' - $Script:WarningPreference = 'Continue' - $Script:ConfirmPreference = 'None' - -#Load WMI Classes - $Baseboard = Get-WmiObject -Namespace "root\CIMv2" -Class "Win32_Baseboard" -Property * | Select-Object -Property * - $Bios = Get-WmiObject -Namespace "root\CIMv2" -Class "Win32_Bios" -Property * | Select-Object -Property * - $ComputerSystem = Get-WmiObject -Namespace "root\CIMv2" -Class "Win32_ComputerSystem" -Property * | Select-Object -Property * - $OperatingSystem = Get-WmiObject -Namespace "root\CIMv2" -Class "Win32_OperatingSystem" -Property * | Select-Object -Property * +If ((Get-AdministrativePrivilege) -eq $False) + { + [System.IO.FileInfo]$ScriptPath = "$($MyInvocation.MyCommand.Path)" -#Retrieve property values - $OSArchitecture = $($OperatingSystem.OSArchitecture).Replace("-bit", "").Replace("32", "86").Insert(0,"x").ToUpper() + $ArgumentList = New-Object -TypeName 'System.Collections.Generic.List[String]' + $ArgumentList.Add('-ExecutionPolicy Bypass') + $ArgumentList.Add('-NoProfile') + $ArgumentList.Add('-NoExit') + $ArgumentList.Add('-NoLogo') + $ArgumentList.Add("-File `"$($ScriptPath.FullName)`"") -#Define variable(s) - $DateTimeLogFormat = 'dddd, MMMM dd, yyyy hh:mm:ss tt' ###Monday, January 01, 2019 10:15:34 AM### - [ScriptBlock]$GetCurrentDateTimeLogFormat = {(Get-Date).ToString($DateTimeLogFormat)} - $DateTimeFileFormat = 'yyyyMMdd_hhmmsstt' ###20190403_115354AM### - [ScriptBlock]$GetCurrentDateTimeFileFormat = {(Get-Date).ToString($DateTimeFileFormat)} - [System.IO.FileInfo]$ScriptPath = "$($MyInvocation.MyCommand.Definition)" - [System.IO.FileInfo]$ScriptLogPath = "$($LogDir.FullName)\$($ScriptPath.BaseName)_$($GetCurrentDateTimeFileFormat.Invoke()).log" - [System.IO.DirectoryInfo]$ScriptDirectory = "$($ScriptPath.Directory.FullName)" - [System.IO.DirectoryInfo]$FunctionsDirectory = "$($ScriptDirectory.FullName)\Functions" - [System.IO.DirectoryInfo]$ModulesDirectory = "$($ScriptDirectory.FullName)\Modules" - [System.IO.DirectoryInfo]$ToolsDirectory = "$($ScriptDirectory.FullName)\Tools\$($OSArchitecture)" - $IsWindowsPE = Test-Path -Path 'HKLM:\SYSTEM\ControlSet001\Control\MiniNT' -ErrorAction SilentlyContinue - -#Log any useful information - $LogMessage = "IsWindowsPE = $($IsWindowsPE.ToString())`r`n" - Write-Verbose -Message "$($LogMessage)" -Verbose - - $LogMessage = "Script Path = $($ScriptPath.FullName)`r`n" - Write-Verbose -Message "$($LogMessage)" -Verbose + $Null = Start-Process -FilePath "$([System.Environment]::SystemDirectory)\WindowsPowershell\v1.0\powershell.exe" -WorkingDirectory "$([System.Environment]::SystemDirectory)" -ArgumentList ($ArgumentList.ToArray()) -WindowStyle Normal -Verb RunAs -PassThru + } +Else + { + #Determine the date and time we executed the function + $ScriptStartTime = (Get-Date) - $LogMessage = "Script Directory = $($ScriptDirectory.FullName)`r`n" - Write-Verbose -Message "$($LogMessage)" -Verbose + #Define Default Action Preferences + $Script:DebugPreference = 'SilentlyContinue' + $Script:ErrorActionPreference = 'Stop' + $Script:VerbosePreference = 'SilentlyContinue' + $Script:WarningPreference = 'Continue' + $Script:ConfirmPreference = 'None' + $Script:WhatIfPreference = $False + + #Load WMI Classes + $Baseboard = Get-WmiObject -Namespace "root\CIMv2" -Class "Win32_Baseboard" -Property * + $Bios = Get-WmiObject -Namespace "root\CIMv2" -Class "Win32_Bios" -Property * + $ComputerSystem = Get-WmiObject -Namespace "root\CIMv2" -Class "Win32_ComputerSystem" -Property * + $OperatingSystem = Get-WmiObject -Namespace "root\CIMv2" -Class "Win32_OperatingSystem" -Property * + $MSSystemInformation = Get-WmiObject -Namespace "root\WMI" -Class "MS_SystemInformation" -Property * + + #Retrieve property values + $OSArchitecture = $($OperatingSystem.OSArchitecture).Replace("-bit", "").Replace("32", "86").Insert(0,"x").ToUpper() + + #Define variable(s) + $DateTimeLogFormat = 'dddd, MMMM dd, yyyy @ hh:mm:ss.FFF tt' ###Monday, January 01, 2019 @ 10:15:34.000 AM### + [ScriptBlock]$GetCurrentDateTimeLogFormat = {(Get-Date).ToString($DateTimeLogFormat)} + $DateTimeMessageFormat = 'MM/dd/yyyy HH:mm:ss.FFF' ###03/23/2022 11:12:48.347### + [ScriptBlock]$GetCurrentDateTimeMessageFormat = {(Get-Date).ToString($DateTimeMessageFormat)} + $DateFileFormat = 'yyyyMMdd' ###20190403### + [ScriptBlock]$GetCurrentDateFileFormat = {(Get-Date).ToString($DateFileFormat)} + $DateTimeFileFormat = 'yyyyMMdd_HHmmss' ###20190403_115354### + [ScriptBlock]$GetCurrentDateTimeFileFormat = {(Get-Date).ToString($DateTimeFileFormat)} + [System.IO.FileInfo]$ScriptPath = "$($MyInvocation.MyCommand.Definition)" + [System.IO.DirectoryInfo]$ScriptDirectory = "$($ScriptPath.Directory.FullName)" + [System.IO.DirectoryInfo]$ContentDirectory = "$($ScriptDirectory.FullName)\Content" + [System.IO.DirectoryInfo]$FunctionsDirectory = "$($ScriptDirectory.FullName)\Functions" + [System.IO.DirectoryInfo]$ModulesDirectory = "$($ScriptDirectory.FullName)\Modules" + [System.IO.DirectoryInfo]$ToolsDirectory = "$($ScriptDirectory.FullName)\Tools" + [System.IO.DirectoryInfo]$ToolsDirectory_OSAll = "$($ToolsDirectory.FullName)\All" + [System.IO.DirectoryInfo]$ToolsDirectory_OSArchSpecific = "$($ToolsDirectory.FullName)\$($OSArchitecture)" + [System.IO.DirectoryInfo]$System32Directory = [System.Environment]::SystemDirectory + [System.IO.DirectoryInfo]$ProgramFilesDirectory = "$($Env:SystemDrive)\Program Files" + [System.IO.DirectoryInfo]$ProgramFilesx86Directory = "$($Env:SystemDrive)\Program Files (x86)" + [System.IO.FileInfo]$PowershellPath = "$($System32Directory.FullName)\WindowsPowershell\v1.0\powershell.exe" + [System.IO.DirectoryInfo]$System32Directory = "$([System.Environment]::SystemDirectory)" + $IsWindowsPE = Test-Path -Path 'HKLM:\SYSTEM\ControlSet001\Control\MiniNT' -ErrorAction SilentlyContinue + [System.Text.RegularExpressions.RegexOptions[]]$RegexOptions = [System.Text.RegularExpressions.RegexOptions]::IgnoreCase, [System.Text.RegularExpressions.RegexOptions]::Multiline + [ScriptBlock]$GetRandomGUID = {[System.GUID]::NewGUID().GUID.ToString().ToUpper()} + [String]$ParameterSetName = "$($PSCmdlet.ParameterSetName)" + $TextInfo = (Get-Culture).TextInfo + $Script:LASTEXITCODE = 0 + $TerminationCodes = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary' + $TerminationCodes.Add('Success', @(0)) + $TerminationCodes.Add('Warning', @(5000..5999)) + $TerminationCodes.Add('Error', @(6000..6999)) + $Script:WarningCodeIndex = 0 + [ScriptBlock]$GetAvailableWarningCode = {$TerminationCodes.Warning[$Script:WarningCodeIndex]; $Script:WarningCodeIndex++} + $Script:ErrorCodeIndex = 0 + [ScriptBlock]$GetAvailableErrorCode = {$TerminationCodes.Error[$Script:ErrorCodeIndex]; $Script:ErrorCodeIndex++} + $LoggingDetails = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary' + $LoggingDetails.Add('LogMessage', $Null) + $LoggingDetails.Add('WarningMessage', $Null) + $LoggingDetails.Add('ErrorMessage', $Null) + $RegularExpressionTable = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary' + $RegularExpressionTable.Base64 = '^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{4})$' -As [Regex] + $CommonParameterList = New-Object -TypeName 'System.Collections.Generic.List[String]' + $CommonParameterList.AddRange([System.Management.Automation.PSCmdlet]::CommonParameters) + $CommonParameterList.AddRange([System.Management.Automation.PSCmdlet]::OptionalCommonParameters) + + #Define the error handling definition + [ScriptBlock]$ErrorHandlingDefinition = { + If (($Null -ieq $Script:LASTEXITCODE) -or ($Script:LASTEXITCODE -eq 0)) + { + [Int]$Script:LASTEXITCODE = $GetAvailableErrorCode.InvokeReturnAsIs() + } + + $ErrorMessageList = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary' + $ErrorMessageList.Add('Message', $_.Exception.Message) + $ErrorMessageList.Add('Category', $_.Exception.ErrorRecord.FullyQualifiedErrorID) + $ErrorMessageList.Add('ExitCode', $Script:LASTEXITCODE) + $ErrorMessageList.Add('Script', $_.InvocationInfo.ScriptName) + $ErrorMessageList.Add('LineNumber', $_.InvocationInfo.ScriptLineNumber) + $ErrorMessageList.Add('LinePosition', $_.InvocationInfo.OffsetInLine) + $ErrorMessageList.Add('Code', $_.InvocationInfo.Line.Trim()) + + ForEach ($ErrorMessage In $ErrorMessageList.GetEnumerator()) + { + $LoggingDetails.ErrorMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - ERROR: $($ErrorMessage.Key): $($ErrorMessage.Value)" + Write-Warning -Message ($LoggingDetails.ErrorMessage) -Verbose + } + + Switch (($ContinueOnError.IsPresent -eq $False) -or ($ContinueOnError -eq $False)) + { + {($_ -eq $True)} + { + Throw + } + } + } -#Log task sequence variables if debug mode is enabled within the task sequence - Try - { - [System.__ComObject]$TSEnvironment = New-Object -ComObject "Microsoft.SMS.TSEnvironment" - - If ($TSEnvironment -ine $Null) - { - $IsRunningTaskSequence = $True - } - } - Catch - { - $IsRunningTaskSequence = $False - } - -#Start transcripting (Logging) - Try - { - If ($LogDir.Exists -eq $False) {[Void][System.IO.Directory]::CreateDirectory($LogDir.FullName)} - Start-Transcript -Path "$($ScriptLogPath.FullName)" -IncludeInvocationHeader -Force -Verbose - } - Catch - { - If ([String]::IsNullOrEmpty($_.Exception.Message)) {$ExceptionMessage = "$($_.Exception.Errors.Message)"} Else {$ExceptionMessage = "$($_.Exception.Message)"} - - $ErrorMessage = "[Error Message: $($ExceptionMessage)][ScriptName: $($_.InvocationInfo.ScriptName)][Line Number: $($_.InvocationInfo.ScriptLineNumber)][Line Position: $($_.InvocationInfo.OffsetInLine)][Code: $($_.InvocationInfo.Line.Trim())]" - Write-Error -Message "$($ErrorMessage)" - } - -#Log any useful information - $LogMessage = "IsWindowsPE = $($IsWindowsPE.ToString())" - Write-Verbose -Message "$($LogMessage)" -Verbose - - $LogMessage = "Script Path = $($ScriptPath.FullName)" - Write-Verbose -Message "$($LogMessage)" -Verbose - - $DirectoryVariables = Get-Variable | Where-Object {($_.Value -ine $Null) -and ($_.Value -is [System.IO.DirectoryInfo])} - - ForEach ($DirectoryVariable In $DirectoryVariables) - { - $LogMessage = "$($DirectoryVariable.Name) = $($DirectoryVariable.Value.FullName)" - Write-Verbose -Message "$($LogMessage)" -Verbose - } - -#region Import Dependency Modules -$Modules = Get-Module -Name "$($ModulesDirectory.FullName)\*" -ListAvailable -ErrorAction Stop - -$ModuleGroups = $Modules | Group-Object -Property @('Name') - -ForEach ($ModuleGroup In $ModuleGroups) - { - $LatestModuleVersion = $ModuleGroup.Group | Sort-Object -Property @('Version') -Descending | Select-Object -First 1 - - If ($LatestModuleVersion -ine $Null) - { - $LogMessage = "Attempting to import dependency powershell module `"$($LatestModuleVersion.Name) [Version: $($LatestModuleVersion.Version.ToString())]`". Please Wait..." - Write-Verbose -Message "$($LogMessage)" -Verbose - Import-Module -Name "$($LatestModuleVersion.Path)" -Global -DisableNameChecking -Force -ErrorAction Stop - } - } -#endregion - -#region Dot Source Dependency Scripts -#Dot source any additional script(s) from the functions directory. This will provide flexibility to add additional functions without adding complexity to the main script and to maintain function consistency. - Try - { - If ($FunctionsDirectory.Exists -eq $True) - { - [String[]]$AdditionalFunctionsFilter = "*.ps1" - - $AdditionalFunctionsToImport = Get-ChildItem -Path "$($FunctionsDirectory.FullName)" -Include ($AdditionalFunctionsFilter) -Recurse -Force | Where-Object {($_ -is [System.IO.FileInfo])} - - $AdditionalFunctionsToImportCount = $AdditionalFunctionsToImport | Measure-Object | Select-Object -ExpandProperty Count - - If ($AdditionalFunctionsToImportCount -gt 0) - { - ForEach ($AdditionalFunctionToImport In $AdditionalFunctionsToImport) - { - Try - { - $LogMessage = "Attempting to dot source dependency script `"$($AdditionalFunctionToImport.Name)`". Please Wait...`r`n`r`nScript Path: `"$($AdditionalFunctionToImport.FullName)`"" - Write-Verbose -Message "$($LogMessage)" -Verbose - - . "$($AdditionalFunctionToImport.FullName)" - } - Catch - { - $ErrorMessage = "[Error Message: $($_.Exception.Message)]`r`n`r`n[ScriptName: $($_.InvocationInfo.ScriptName)]`r`n[Line Number: $($_.InvocationInfo.ScriptLineNumber)]`r`n[Line Position: $($_.InvocationInfo.OffsetInLine)]`r`n[Code: $($_.InvocationInfo.Line.Trim())]" - Write-Error -Message "$($ErrorMessage)" -Verbose - } - } - } - } - } - Catch - { - $ErrorMessage = "[Error Message: $($_.Exception.Message)]`r`n`r`n[ScriptName: $($_.InvocationInfo.ScriptName)]`r`n[Line Number: $($_.InvocationInfo.ScriptLineNumber)]`r`n[Line Position: $($_.InvocationInfo.OffsetInLine)]`r`n[Code: $($_.InvocationInfo.Line.Trim())]" - Write-Error -Message "$($ErrorMessage)" -Verbose - } -#endregion - -#Perform script action(s) - Try - { - #Tasks defined within this block will only execute if a task sequence is running - If (($IsRunningTaskSequence -eq $True)) - { - - } - - #Tasks defined here will execute whether a task sequence is running or not - ###Place the code here - - #Tasks defined here will execute whether only if a task sequence is not running - If ($IsRunningTaskSequence -eq $False) - { - $WarningMessage = "There is no task sequence running.`r`n" - Write-Warning -Message "$($WarningMessage)" -Verbose - } - - #Stop transcripting (Logging) + #Log task sequence variables if debug mode is enabled within the task sequence Try { - Stop-Transcript -Verbose + [System.__ComObject]$TSEnvironment = New-Object -ComObject "Microsoft.SMS.TSEnvironment" + + If ($Null -ine $TSEnvironment) + { + $IsRunningTaskSequence = $True + + [Boolean]$IsConfigurationManagerTaskSequence = [String]::IsNullOrEmpty($TSEnvironment.Value("_SMSTSPackageID")) -eq $False + + Switch ($IsConfigurationManagerTaskSequence) + { + {($_ -eq $True)} + { + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - A Microsoft Endpoint Configuration Manager (MECM) task sequence was detected." + Write-Verbose -Message ($LoggingDetails.LogMessage) -Verbose + } + + {($_ -eq $False)} + { + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - A Microsoft Deployment Toolkit (MDT) task sequence was detected." + Write-Verbose -Message ($LoggingDetails.LogMessage) -Verbose + } + } + } } Catch { - If ([String]::IsNullOrEmpty($_.Exception.Message)) {$ExceptionMessage = "$($_.Exception.Errors.Message)"} Else {$ExceptionMessage = "$($_.Exception.Message)"} - - $ErrorMessage = "[Error Message: $($ExceptionMessage)][ScriptName: $($_.InvocationInfo.ScriptName)][Line Number: $($_.InvocationInfo.ScriptLineNumber)][Line Position: $($_.InvocationInfo.OffsetInLine)][Code: $($_.InvocationInfo.Line.Trim())]" - Write-Error -Message "$($ErrorMessage)" + $IsRunningTaskSequence = $False } - } - Catch - { - If ([String]::IsNullOrEmpty($_.Exception.Message)) {$ExceptionMessage = "$($_.Exception.Errors.Message -Join "`r`n`r`n")"} Else {$ExceptionMessage = "$($_.Exception.Message)"} + + #Determine default parameter value(s) + Switch ($True) + { + {([String]::IsNullOrEmpty($LogDirectory) -eq $True) -or ([String]::IsNullOrWhiteSpace($LogDirectory) -eq $True)} + { + Switch ($IsRunningTaskSequence) + { + {($_ -eq $True)} + { + Switch ($IsConfigurationManagerTaskSequence) + { + {($_ -eq $True)} + { + [String]$_SMSTSLogPath = "$($TSEnvironment.Value('_SMSTSLogPath'))" + } + + {($_ -eq $False)} + { + [String]$_SMSTSLogPath = "$($TSEnvironment.Value('LogPath'))" + } + } + + Switch ([String]::IsNullOrEmpty($_SMSTSLogPath)) + { + {($_ -eq $True)} + { + [System.IO.DirectoryInfo]$TSLogDirectory = "$($Env:Windir)\Temp\SMSTSLog" + } + + {($_ -eq $False)} + { + Switch ($True) + { + {(Test-Path -Path ($_SMSTSLogPath) -PathType Container)} + { + [System.IO.DirectoryInfo]$TSLogDirectory = ($_SMSTSLogPath) + } + + {(Test-Path -Path ($_SMSTSLogPath) -PathType Leaf)} + { + [System.IO.DirectoryInfo]$TSLogDirectory = Split-Path -Path ($_SMSTSLogPath) -Parent + } + } + } + } + + [System.IO.DirectoryInfo]$LogDirectory = "$($TSLogDirectory.FullName)\$($ScriptPath.BaseName)" + } + + {($_ -eq $False)} + { + Switch ($IsWindowsPE) + { + {($_ -eq $True)} + { + [System.IO.FileInfo]$MDTBootImageDetectionPath = "$($Env:SystemDrive)\Deploy\Scripts\Litetouch.wsf" + + [Boolean]$MDTBootImageDetected = Test-Path -Path ($MDTBootImageDetectionPath.FullName) + + Switch ($MDTBootImageDetected) + { + {($_ -eq $True)} + { + [System.IO.DirectoryInfo]$LogDirectory = "$($Env:SystemDrive)\MININT\SMSOSD\OSDLOGS\$($ScriptPath.BaseName)" + } + + {($_ -eq $False)} + { + [System.IO.DirectoryInfo]$LogDirectory = "$($Env:Windir)\Temp\SMSTSLog" + } + } + } + + {($_ -eq $False)} + { + [System.IO.DirectoryInfo]$LogDirectory = "$($Env:Windir)\Logs\Software\$($ScriptPath.BaseName)" + } + } + } + } + } + } + + #Start transcripting (Logging) + [System.IO.FileInfo]$ScriptLogPath = "$($LogDirectory.FullName)\$($ScriptPath.BaseName)_$($GetCurrentDateFileFormat.Invoke()).log" + If ($ScriptLogPath.Directory.Exists -eq $False) {$Null = [System.IO.Directory]::CreateDirectory($ScriptLogPath.Directory.FullName)} + Start-Transcript -Path "$($ScriptLogPath.FullName)" -Force -WhatIf:$False + + #Log any useful information + [String]$CmdletName = $MyInvocation.MyCommand.Name + + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Execution of script `"$($CmdletName)`" began on $($ScriptStartTime.ToString($DateTimeLogFormat))" + Write-Verbose -Message ($LoggingDetails.LogMessage) -Verbose + + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Script Path = $($ScriptPath.FullName)" + Write-Verbose -Message ($LoggingDetails.LogMessage) -Verbose + + [String[]]$AvailableScriptParameters = (Get-Command -Name ($ScriptPath.FullName)).Parameters.GetEnumerator() | Where-Object {($_.Value.Name -inotin $CommonParameterList)} | ForEach-Object {"-$($_.Value.Name):$($_.Value.ParameterType.Name)"} + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Available Script Parameter(s) = $($AvailableScriptParameters -Join ', ')" + Write-Verbose -Message ($LoggingDetails.LogMessage) -Verbose + + [String[]]$SuppliedScriptParameters = $PSBoundParameters.GetEnumerator() | ForEach-Object {"-$($_.Key):$($_.Value.GetType().Name)"} + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Supplied Script Parameter(s) = $($SuppliedScriptParameters -Join ', ')" + Write-Verbose -Message ($LoggingDetails.LogMessage) -Verbose - $ErrorMessage = "[Error Message: $($ExceptionMessage)]`r`n`r`n[ScriptName: $($_.InvocationInfo.ScriptName)]`r`n[Line Number: $($_.InvocationInfo.ScriptLineNumber)]`r`n[Line Position: $($_.InvocationInfo.OffsetInLine)]`r`n[Code: $($_.InvocationInfo.Line.Trim())]`r`n" - If ($ContinueOnError.IsPresent -eq $False) {Throw "$($ErrorMessage)"} + Switch ($True) + { + {([String]::IsNullOrEmpty($ParameterSetName) -eq $False)} + { + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Parameter Set Name = $($ParameterSetName)" + Write-Verbose -Message ($LoggingDetails.LogMessage) -Verbose + } + } + + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Command Line: $((Get-WMIObject -Namespace 'Root\CIMv2' -Class 'Win32_Process' -Filter "ProcessID = $($PID)").CommandLine)" + Write-Verbose -Message ($LoggingDetails.LogMessage) -Verbose + + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - $($PSBoundParameters.Count) command line parameter(s) were specified." + Write-Verbose -Message ($LoggingDetails.LogMessage) -Verbose + + $OperatingSystemDetailsTable = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary' + $OperatingSystemDetailsTable.ProductName = $OperatingSystem.Caption -ireplace '(Microsoft\s+)', '' + $OperatingSystemDetailsTable.Version = $OperatingSystem.Version + $OperatingSystemDetailsTable.Architecture = $OperatingSystem.OSArchitecture + + $OperatingSystemRegistryDetails = New-Object -TypeName 'System.Collections.Generic.List[PSObject]' + $OperatingSystemRegistryDetails.Add((New-Object -TypeName 'PSObject' -Property @{Alias = ''; Path = 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion'; ValueName = 'UBR'; Value = $Null})) + $OperatingSystemRegistryDetails.Add((New-Object -TypeName 'PSObject' -Property @{Alias = 'ReleaseVersion'; Path = 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion'; ValueName = 'ReleaseID'; Value = $Null})) + $OperatingSystemRegistryDetails.Add((New-Object -TypeName 'PSObject' -Property @{Alias = 'ReleaseID'; Path = 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion'; ValueName = 'DisplayVersion'; Value = $Null})) + + ForEach ($OperatingSystemRegistryDetail In $OperatingSystemRegistryDetails) + { + $OperatingSystemRegistryDetail.Value = Try {(Get-Item -Path $OperatingSystemRegistryDetail.Path).GetValue($OperatingSystemRegistryDetail.ValueName)} Catch {} + + :NextOSDetail Switch (([String]::IsNullOrEmpty($OperatingSystemRegistryDetail.Value) -eq $False) -and ([String]::IsNullOrWhiteSpace($OperatingSystemRegistryDetail.Value) -eq $False)) + { + {($_ -eq $True)} + { + Switch ($OperatingSystemRegistryDetail.ValueName) + { + {($_ -ieq 'UBR')} + { + $OperatingSystemDetailsTable.Version = $OperatingSystemDetailsTable.Version + '.' + $OperatingSystemRegistryDetail.Value + + Break NextOSDetail + } + } + + Switch (([String]::IsNullOrEmpty($OperatingSystemRegistryDetail.Alias) -eq $False) -and ([String]::IsNullOrWhiteSpace($OperatingSystemRegistryDetail.Alias) -eq $False)) + { + {($_ -eq $True)} + { + $OperatingSystemDetailsTable.$($OperatingSystemRegistryDetail.Alias) = $OperatingSystemRegistryDetail.Value + } + + Default + { + $OperatingSystemDetailsTable.$($OperatingSystemRegistryDetail.ValueName) = $OperatingSystemRegistryDetail.Value + } + } + } + + Default + { + $OperatingSystemDetailsTable.$($OperatingSystemRegistryDetail.ValueName) = $OperatingSystemRegistryDetail.Value + } + } + } + + ForEach ($OperatingSystemDetail In $OperatingSystemDetailsTable.GetEnumerator()) + { + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - $($OperatingSystemDetail.Key): $($OperatingSystemDetail.Value)" + Write-Verbose -Message ($LoggingDetails.LogMessage) -Verbose + } + + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Powershell Version: $($PSVersionTable.PSVersion.ToString())" + Write-Verbose -Message ($LoggingDetails.LogMessage) -Verbose + + $ExecutionPolicyList = Get-ExecutionPolicy -List + + For ($ExecutionPolicyListIndex = 0; $ExecutionPolicyListIndex -lt $ExecutionPolicyList.Count; $ExecutionPolicyListIndex++) + { + $ExecutionPolicy = $ExecutionPolicyList[$ExecutionPolicyListIndex] + + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - The powershell execution policy is currently set to `"$($ExecutionPolicy.ExecutionPolicy.ToString())`" for the `"$($ExecutionPolicy.Scope.ToString())`" scope." + Write-Verbose -Message ($LoggingDetails.LogMessage) -Verbose + } + + #Log hardware information + $MSSystemInformationMembers = $MSSystemInformation.PSObject.Properties | Where-Object {($_.MemberType -imatch '^NoteProperty$|^Property$') -and ($_.Name -imatch '^Base.*|Bios.*|System.*$') -and ($_.Name -inotmatch '^.*Major.*|.*Minor.*|.*Properties.*$')} | Sort-Object -Property @('Name') + + Switch ($MSSystemInformationMembers.Count -gt 0) + { + {($_ -eq $True)} + { + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Attempting to display device information properties from the `"$($MSSystemInformation.__CLASS)`" WMI class located within the `"$($MSSystemInformation.__NAMESPACE)`" WMI namespace. Please Wait..." + Write-Verbose -Message ($LoggingDetails.LogMessage) -Verbose + + ForEach ($MSSystemInformationMember In $MSSystemInformationMembers) + { + [String]$MSSystemInformationMemberName = ($MSSystemInformationMember.Name) + [String]$MSSystemInformationMemberValue = $MSSystemInformation.$($MSSystemInformationMemberName) + + Switch ([String]::IsNullOrEmpty($MSSystemInformationMemberValue)) + { + {($_ -eq $False)} + { + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - $($MSSystemInformationMemberName) = $($MSSystemInformationMemberValue)" + Write-Verbose -Message ($LoggingDetails.LogMessage) -Verbose + } + } + } + } + + Default + { + $LoggingDetails.WarningMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - The `"MSSystemInformation`" WMI class could not be found." + Write-Warning -Message ($LoggingDetails.WarningMessage) -Verbose + } + } + + #region Log Cleanup + [Int]$MaximumLogHistory = 3 + + $LogList = Get-ChildItem -Path ($LogDirectory.FullName) -Filter "$($ScriptPath.BaseName)_*" -Recurse -Force | Where-Object {($_ -is [System.IO.FileInfo])} + + $SortedLogList = $LogList | Sort-Object -Property @('LastWriteTime') -Descending | Select-Object -Skip ($MaximumLogHistory) + + Switch ($SortedLogList.Count -gt 0) + { + {($_ -eq $True)} + { + $LoggingDetails.WarningMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - There are $($SortedLogList.Count) log file(s) requiring cleanup." + Write-Warning -Message ($LoggingDetails.WarningMessage) -Verbose + + For ($SortedLogListIndex = 0; $SortedLogListIndex -lt $SortedLogList.Count; $SortedLogListIndex++) + { + Try + { + $Log = $SortedLogList[$SortedLogListIndex] + + $LogAge = New-TimeSpan -Start ($Log.LastWriteTime) -End (Get-Date) + + $LoggingDetails.WarningMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Attempting to cleanup log file `"$($Log.FullName)`". Please Wait... [Last Modified: $($Log.LastWriteTime.ToString($DateTimeMessageFormat))] [Age: $($LogAge.Days.ToString()) day(s); $($LogAge.Hours.ToString()) hours(s); $($LogAge.Minutes.ToString()) minute(s); $($LogAge.Seconds.ToString()) second(s)]." + Write-Warning -Message ($LoggingDetails.WarningMessage) -Verbose + + $Null = [System.IO.File]::Delete($Log.FullName) + } + Catch + { + + } + } + } + + Default + { + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - There are $($SortedLogList.Count) log file(s) requiring cleanup." + Write-Verbose -Message ($LoggingDetails.LogMessage) -Verbose + } + } + #endregion + + #region Import Dependency Modules + If (($ModulesDirectory.Exists -eq $True) -and ($ModulesDirectory.GetDirectories().Count -gt 0)) + { + $AvailableModules = Get-Module -Name "$($ModulesDirectory.FullName)\*" -ListAvailable -ErrorAction Stop + + $AvailableModuleGroups = $AvailableModules | Group-Object -Property @('Name') + + ForEach ($AvailableModuleGroup In $AvailableModuleGroups) + { + $LatestAvailableModuleVersion = $AvailableModuleGroup.Group | Sort-Object -Property @('Version') -Descending | Select-Object -First 1 + + If ($Null -ine $LatestAvailableModuleVersion) + { + Switch ($LatestAvailableModuleVersion.RequiredModules.Count -gt 0) + { + {($_ -eq $True)} + { + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - $($LatestAvailableModuleVersion.RequiredModules.Count) prerequisite powershell module(s) need to be imported before the powershell of `"$($LatestAvailableModuleVersion.Name)`" can be imported." + Write-Verbose -Message ($LoggingDetails.LogMessage) -Verbose + + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Prequisite Module List: $($LatestAvailableModuleVersion.RequiredModules.Name -Join '; ')" + Write-Verbose -Message ($LoggingDetails.LogMessage) -Verbose + + ForEach ($RequiredModule In $LatestAvailableModuleVersion.RequiredModules) + { + Switch ($RequiredModule.Name -iin $AvailableModules.Name) + { + {($_ -eq $True)} + { + Switch ($Null -ine (Get-Module -Name $RequiredModule.Name -ErrorAction SilentlyContinue)) + { + {($_ -eq $True)} + { + $RequiredModuleDetails = $AvailableModules | Where-Object {($_.Name -ieq $RequiredModule.Name)} + + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Attempting to import prerequisite powershell module `"$($RequiredModuleDetails.Name)`" [Version: $($RequiredModuleDetails.Version.ToString())]. Please Wait..." + Write-Verbose -Message ($LoggingDetails.LogMessage) -Verbose + + Import-Module -Name "$($RequiredModuleDetails.Path)" -Global -DisableNameChecking -Force -ErrorAction Stop + } + } + } + } + } + } + } + + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Attempting to import dependency powershell module `"$($LatestAvailableModuleVersion.Name)`" [Version: $($LatestAvailableModuleVersion.Version.ToString())]. Please Wait..." + Write-Verbose -Message ($LoggingDetails.LogMessage) -Verbose + + Import-Module -Name "$($LatestAvailableModuleVersion.Path)" -Global -DisableNameChecking -Force -ErrorAction Stop + } + } + } + #endregion + + #region Download And Install Dependency Package Provider(s) and Module(s) + Switch ($IsWindowsPE) + { + {($_ -eq $False)} + { + $RequiredPackageProviders = New-Object -TypeName 'System.Collections.Generic.List[String]' + #$RequiredPackageProviders.Add('NuGet') + #$RequiredPackageProviders.Add('PowershellGet') + + $RequiredModules = New-Object -TypeName 'System.Collections.Generic.List[String]' + #$RequiredModules.Add('ImportExcel') + + Switch ($True) + { + {($RequiredPackageProviders.Count -gt 0)} + { + [System.Net.SecurityProtocolType]$DesiredSecurityProtocol = [System.Net.SecurityProtocolType]::TLS12 + + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Attempting to set the desired security protocol to `"$($DesiredSecurityProtocol.ToString().ToUpper())`". Please Wait..." + Write-Verbose -Message ($LoggingDetails.LogMessage) -Verbose + + $Null = [System.Net.ServicePointManager]::SecurityProtocol = ($DesiredSecurityProtocol) + + ForEach ($RequiredPackageProvider In $RequiredPackageProviders) + { + $RequiredPackageProviderInfo = Get-PackageProvider -Name ($RequiredPackageProvider) -ListAvailable -Force -ErrorAction SilentlyContinue + + $LatestRequiredPackageProviderInfo = Find-PackageProvider -Name ($RequiredPackageProvider) -Force + + Switch (($RequiredPackageProviderInfo.Version -lt $LatestRequiredPackageProviderInfo.Version) -or ($Null -ieq $RequiredPackageProviderInfo)) + { + {($_ -eq $True)} + { + [String]$InstallPackageProvider_Name = ($RequiredPackageProvider) + [String]$InstallPackageProvider_Scope = "AllUsers" + [Switch]$InstallPackageProvider_Force = $True + [Switch]$InstallPackageProvider_Verbose = $False + [System.Management.Automation.Actionpreference]$InstallPackageProvider_ErrorAction = [System.Management.Automation.Actionpreference]::Stop + [Switch]$InstallPackageProvider_Confirm = $False + + $InstallPackageProviderParameters = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary' + $InstallPackageProviderParameters.Add('Name', ($InstallPackageProvider_Name)) + $InstallPackageProviderParameters.Add('Scope', ($InstallPackageProvider_Scope)) + $InstallPackageProviderParameters.Add('Force', ($InstallPackageProvider_Force)) + $InstallPackageProviderParameters.Add('Verbose', ($InstallPackageProvider_Verbose)) + $InstallPackageProviderParameters.Add('ErrorAction', ($InstallPackageProvider_ErrorAction)) + $InstallPackageProviderParameters.Add('Confirm', ($InstallPackageProvider_Confirm)) + + [String]$CommandName = "Install-PackageProvider" + + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Attempting to install the latest version [Version: $($LatestRequiredPackageProviderInfo.Version)] of the `"$($RequiredPackageProvider)`" package provider. Please Wait..." + Write-Verbose -Message ($LoggingDetails.LogMessage) -Verbose + + $InstallPackageProviderInfo = Install-PackageProvider @InstallPackageProviderParameters + } + + Default + { + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - The `"$($RequiredPackageProvider)`" package provider does NOT require an update." + Write-Verbose -Message ($LoggingDetails.LogMessage) -Verbose + } + } + } + } + + {($RequiredModules.Count -gt 0)} + { + ForEach ($RequiredModule In $RequiredModules) + { + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Attempting to search for the existence of powershell module `"$($RequiredModule)`" on device `"$($Env:ComputerName)`". Please Wait..." + Write-Verbose -Message ($LoggingDetails.LogMessage) -Verbose + + $RequiredModuleExists = Get-Module -Name ($RequiredModule) -ListAvailable + + $RequiredModuleCount = $RequiredModuleExists | Measure-Object | Select-Object -ExpandProperty Count + + Switch ($RequiredModuleCount -eq 0) + { + {($_ -eq $True)} + { + [String[]]$InstallModule_Name = ($RequiredModule) + [String]$InstallModule_Scope = "AllUsers" + [Switch]$InstallModule_AllowClobber = $True + [Switch]$InstallModule_Force = $True + [Switch]$InstallModule_PassThru = $True + [Switch]$InstallModule_Confirm = $False + [Switch]$InstallModule_Verbose = $False + [System.Management.Automation.Actionpreference]$InstallModule_ErrorAction = [System.Management.Automation.Actionpreference]::Stop + + $InstallModuleParameters = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary' + $InstallModuleParameters.Add('Name', ($InstallModule_Name)) + $InstallModuleParameters.Add('Scope', ($InstallModule_Scope)) + $InstallModuleParameters.Add('AllowClobber', ($InstallModule_AllowClobber)) + $InstallModuleParameters.Add('Force', ($InstallModule_Force)) + $InstallModuleParameters.Add('PassThru', ($InstallModule_PassThru)) + $InstallModuleParameters.Add('Confirm', ($InstallModule_Confirm)) + $InstallModuleParameters.Add('Verbose', ($InstallModule_Verbose)) + $InstallModuleParameters.Add('ErrorAction', ($InstallModule_ErrorAction)) + + $LoggingDetails.WarningMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - The powershell module `"$($RequiredModule)`" DOES NOT exist on device `"$($Env:ComputerName)`" and requires installation. Please Wait..." + Write-Warning -Message ($LoggingDetails.WarningMessage) -Verbose + + $InstallModuleInfo = Install-Module @InstallModuleParameters + } + + Default + { + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - The powershell module `"$($RequiredModule)`" already exists on device `"$($Env:ComputerName)`"." + Write-Verbose -Message ($LoggingDetails.LogMessage) -Verbose + + $Null = Import-Module -Name ($RequiredModule) -Force -DisableNameChecking + } + } + } + } + } + } + } + #endregion + + #region Dot Source Dependency Scripts + #Dot source any additional script(s) from the functions directory. This will provide flexibility to add additional functions without adding complexity to the main script and to maintain function consistency. + Try + { + If ($FunctionsDirectory.Exists -eq $True) + { + $AdditionalFunctionsFilter = New-Object -TypeName 'System.Collections.Generic.List[String]' + $AdditionalFunctionsFilter.Add('*.ps1') + + $AdditionalFunctionsToImport = Get-ChildItem -Path "$($FunctionsDirectory.FullName)" -Include ($AdditionalFunctionsFilter) -Recurse -Force | Where-Object {($_ -is [System.IO.FileInfo])} + + $AdditionalFunctionsToImportCount = $AdditionalFunctionsToImport | Measure-Object | Select-Object -ExpandProperty Count + + If ($AdditionalFunctionsToImportCount -gt 0) + { + ForEach ($AdditionalFunctionToImport In $AdditionalFunctionsToImport) + { + Try + { + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Attempting to dot source the functions contained within the dependency script `"$($AdditionalFunctionToImport.Name)`". Please Wait... [Script Path: `"$($AdditionalFunctionToImport.FullName)`"]" + Write-Verbose -Message ($LoggingDetails.LogMessage) -Verbose + + . "$($AdditionalFunctionToImport.FullName)" + } + Catch + { + $ErrorHandlingDefinition.Invoke() + } + } + } + } + } + Catch + { + $ErrorHandlingDefinition.Invoke() + } + #endregion + + #region Load any required libraries + [System.IO.DirectoryInfo]$LibariesDirectory = "$($FunctionsDirectory.FullName)\Libraries" + + Switch ([System.IO.Directory]::Exists($LibariesDirectory.FullName)) + { + {($_ -eq $True)} + { + $LibraryPatternList = New-Object -TypeName 'System.Collections.Generic.List[String]' + #$LibraryPatternList.Add('YourDotNETLibrary.dll') + + Switch ($LibraryPatternList.Count -gt 0) + { + {($_ -eq $True)} + { + $LibraryList = Get-ChildItem -Path ($LibariesDirectory.FullName) -Include ($LibraryPatternList.ToArray()) -Recurse -Force | Where-Object {($_ -is [System.IO.FileInfo])} + + $LibraryListCount = ($LibraryList | Measure-Object).Count + + Switch ($LibraryListCount -gt 0) + { + {($_ -eq $True)} + { + For ($LibraryListIndex = 0; $LibraryListIndex -lt $LibraryListCount; $LibraryListIndex++) + { + $Library = $LibraryList[$LibraryListIndex] + + [Byte[]]$LibraryBytes = [System.IO.File]::ReadAllBytes($Library.FullName) + + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Attempting to load assembly `"$($Library.FullName)`". Please Wait..." + Write-Verbose -Message ($LoggingDetails.LogMessage) -Verbose + + $Null = [System.Reflection.Assembly]::Load($LibraryBytes) + } + } + } + } + } + } + } + #endregion + + #Perform script action(s) + Try + { + #If necessary, create, get, and or set any task sequence variable(s). + Switch ($IsRunningTaskSequence) + { + {($_ -eq $True)} + { + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - A task sequence is currently running." + Write-Verbose -Message ($LoggingDetails.LogMessage) -Verbose + + $TSVariableTable = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary' + + $TaskSequenceVariableRetrievalList = New-Object -TypeName 'System.Collections.Generic.List[String]' + + Switch ($TaskSequenceVariables.Count -gt 0) + { + {($_ -eq $True)} + { + ForEach ($TaskSequenceVariable In $TaskSequenceVariables) + { + $TaskSequenceVariableRetrievalList.Add($TaskSequenceVariable) + } + } + } + + $TSVariableTable = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary' + + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Attempting to retrieve the task sequence variable list. Please Wait..." + Write-Verbose -Message ($LoggingDetails.LogMessage) -Verbose + + Switch ($TaskSequenceVariableRetrievalList.Count -gt 0) + { + {($_ -eq $True)} + { + $TSVariableList = $TSEnvironment.GetVariables() | Where-Object {($_ -iin $TaskSequenceVariableRetrievalList)} | Sort-Object + } + + Default + { + $TSVariableList = $TSEnvironment.GetVariables() | Sort-Object + } + } + + ForEach ($TSVariable In $TSVariableList) + { + $TSVariableName = $TSVariable + $TSVariableValue = $TSEnvironment.Value($TSVariableName) + + Switch ($True) + { + {($TSVariableName -inotmatch '(^_SMSTSTaskSequence$)|(^TaskSequence$)|(^.*Pass.*word.*$)')} + { + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Attempting to retrieve the value of task sequence variable `"$($TSVariableName)`". Please Wait... [Reference: `$TSVariableTable.`'$($TSVariableName)`']" + Write-Verbose -Message ($LoggingDetails.LogMessage) + } + + {($TSVariableTable.Contains($TSVariableName) -eq $False)} + { + $TSVariableTable.Add($TSVariableName, $TSVariableValue) + } + } + } + } + } + + ###Place any custom code starting here (Tasks defined here will execute whether a task sequence is running or not)### + + + #If necessary, create, get, and or set any task sequence variable(s). + Switch ($IsRunningTaskSequence) + { + {($_ -eq $True)} + { + ForEach ($TSVariable In $TSVariableTable.GetEnumerator()) + { + [String]$TSVariableName = "$($TSVariable.Key)" + [String]$TSVariableCurrentValue = $TSEnvironment.Value($TSVariableName) + [String]$TSVariableNewValue = "$($TSVariable.Value -Join ',')" + + Switch ($TSVariableCurrentValue -ine $TSVariableNewValue) + { + {($_ -eq $True)} + { + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Attempting to set the task sequence variable of `"$($TSVariableName)`". Please Wait..." + Write-Verbose -Message ($LoggingDetails.LogMessage) -Verbose + + $Null = $TSEnvironment.Value($TSVariableName) = "$($TSVariableNewValue)" + } + } + } + + $Null = [System.Runtime.Interopservices.Marshal]::ReleaseComObject($TSEnvironment) + } + + {($_ -eq $False)} + { + $LoggingDetails.WarningMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - There is no task sequence running." + Write-Warning -Message ($LoggingDetails.WarningMessage) -Verbose + } + } + + $Script:LASTEXITCODE = $TerminationCodes.Success[0] + } + Catch + { + $ErrorHandlingDefinition.Invoke() + } + Finally + { + Try + { + #Determine the date and time the function completed execution + $ScriptEndTime = (Get-Date) + + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Script execution of `"$($CmdletName)`" ended on $($ScriptEndTime.ToString($DateTimeLogFormat))" + Write-Verbose -Message ($LoggingDetails.LogMessage) -Verbose + + #Log the total script execution time + $ScriptExecutionTimespan = New-TimeSpan -Start ($ScriptStartTime) -End ($ScriptEndTime) + + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Script execution took $($ScriptExecutionTimespan.Hours.ToString()) hour(s), $($ScriptExecutionTimespan.Minutes.ToString()) minute(s), $($ScriptExecutionTimespan.Seconds.ToString()) second(s), and $($ScriptExecutionTimespan.Milliseconds.ToString()) millisecond(s)" + Write-Verbose -Message ($LoggingDetails.LogMessage) -Verbose + + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Exiting script `"$($ScriptPath.FullName)`" with exit code $($Script:LASTEXITCODE)." + Write-Verbose -Message ($LoggingDetails.LogMessage) -Verbose + + Stop-Transcript + } + Catch + { + + } + } } \ No newline at end of file diff --git a/OSD-ScriptTemplate.vbs b/OSD-ScriptTemplate.vbs new file mode 100644 index 0000000..b23503e --- /dev/null +++ b/OSD-ScriptTemplate.vbs @@ -0,0 +1,145 @@ +'Set object values + Set oArguments = WScript.Arguments.Named + Set oFSO = CreateObject("Scripting.FileSystemObject") + Set oShell = CreateObject("WScript.Shell") + Set oCMD = oFSO.GetFile(oShell.ExpandEnvironmentStrings("%COMSPEC%")) + Set oPowershell = oFSO.GetFile(oShell.ExpandEnvironmentStrings("%WINDIR%") & "\System32\WindowsPowershell\v1.0\powershell.exe") + +'Define ASCII Characters + chrSpace = Chr(32) + chrSingleQuote = Chr(39) + chrDoubleQuote = Chr(34) + +'Define Variable(s) + ScriptInterpreter = CreateObject("Scripting.FileSystemObject").GetFileName(WScript.FullName) + If (oArguments.Exists("Debug") = True) Then DebugMode = True End If + +'Dynamically convert named VBScript arguments to their eqivalent powershell format +oArgumentCount = oArguments.Count + +If (oArgumentCount > 0) Then + StringBuilder = "" + + oArgumentCounter = 1 + + Set ArgumentNameExpression = New RegExp + + With ArgumentNameExpression + .Pattern = "^Debug$" + .IgnoreCase = True + .Global = False + .Multiline = False + End With + + Set ArgumentValueExpression = New RegExp + + With ArgumentValueExpression + .Pattern = "^.*True|.*False$" + .IgnoreCase = True + .Global = False + .Multiline = False + End With + + For Each oArgument In oArguments + ArgumentName = Trim(oArgument) + ArgumentValue = oArguments.Item(oArgument) + + If (Not(ArgumentNameExpression.Test(ArgumentName))) Then + Select Case ((Len(ArgumentValue) > 0) And (Not(ArgumentValueExpression.Test(ArgumentValue)))) + Case True + If (InStr(ArgumentValue, ",") > 0) Then + ArgumentValueParts = Split(ArgumentValue, ",") + + ArgumentValuePartsCount = UBound(ArgumentValueParts) + + ArgumentValuePartStringBuilder = "" + + ArgumentValuePartCounter = 0 + + For Each ArgumentValuePart In ArgumentValueParts + ArgumentValuePartFormat = chrDoubleQuote & ArgumentValuePart & chrDoubleQuote + + ArgumentValuePartStringBuilder = ArgumentValuePartStringBuilder & ArgumentValuePartFormat + + If ((ArgumentValuePartsCount > 0) And (ArgumentValuePartCounter < ArgumentValuePartsCount)) Then + ArgumentValuePartStringBuilder = ArgumentValuePartStringBuilder & "," + ArgumentValuePartCounter = ArgumentValuePartCounter + 1 + End If + Next + + ParameterFormat = "-" & ArgumentName & chrSpace & ArgumentValuePartStringBuilder + ElseIf (IsNumeric(ArgumentValue) = True) Then + ParameterFormat = "-" & ArgumentName & chrSpace & ArgumentValue + Else + ParameterFormat = "-" & ArgumentName & chrSpace & chrDoubleQuote & ArgumentValue & chrDoubleQuote + End If + Case False + ParameterFormat = "-" & ArgumentName + End Select + + If (Len(ParameterFormat) > 0) Then + + If ((oArgumentCount > 1) And (oArgumentCounter < oArgumentCount)) Then + StringBuilder = StringBuilder & chrSpace + End If + + StringBuilder = StringBuilder & ParameterFormat + + ArgumentCounter = oArgumentCounter + 1 + + ParameterFormat = Null + End If + End If + Next + + StringBuilderResult = Trim(StringBuilder) + + If (Len(StringBuilderResult) > 0) Then + PowershellScriptParameters = StringBuilderResult & ";" & chrSpace & "[System.Environment]::Exit(($LASTEXITCODE -Bor [Int](-Not $? -And -Not $LASTEXITCODE)))" + Else + If (DebugMode = True) Then + WScript.Echo("Debug mode is enabled. The specified command will NOT be executed.") + End If + + PowershellScriptParameters = ";" & chrSpace & "[System.Environment]::Exit(($LASTEXITCODE -Bor [Int](-Not $? -And -Not $LASTEXITCODE)))" + End If + + Set ArgumentNameExpression = Nothing + + Set ArgumentValueExpression = Nothing +Else + If (DebugMode = True) Then + WScript.Echo(oArgumentCount & chrSpace & "arguments were specified.") + End If +End If + +'Define Additional Variable(s) + Set ScriptPath = oFSO.GetFile(WScript.ScriptFullName) + ScriptDirectory = ScriptPath.ParentFolder + System32Directory = oShell.ExpandEnvironmentStrings("%WINDIR%") & "\System32" + CommandPromptExecutionParameters = oCMD.Name & chrSpace & "/c" + PowershellExecutionParameters = chrDoubleQuote & oPowershell.Path & chrDoubleQuote & chrSpace & "-ExecutionPolicy Bypass -NonInteractive -NoProfile -NoLogo -WindowStyle Hidden -Command" + PowershellScriptPath = ScriptDirectory & "\" & oFSO.GetBaseName(ScriptPath) & ".ps1" + +'Execute Powershell Script + If (Len(PowershellScriptParameters) > 0) Then + Command = PowershellExecutionParameters & chrSpace & chrDoubleQuote & "&" & chrSpace & chrSingleQuote & PowershellScriptPath & chrSingleQuote & chrSpace & PowershellScriptParameters & chrDoubleQuote + Else + Command = PowershellExecutionParameters & chrSpace & chrDoubleQuote & "&" & chrSpace & chrSingleQuote & PowershellScriptPath & chrSingleQuote & chrDoubleQuote + End If + + If ((ScriptInterpreter = "cscript.exe") Or (DebugMode = True)) Then + WScript.Echo("Command:" & chrSpace & Command) + End If + + If (DebugMode = False) Then + RunCommand = oShell.Run(Command, 0, True) + End If + + If ((ScriptInterpreter = "cscript.exe") Or (DebugMode = True)) And (IsEmpty(RunCommand) = False) Then + WScript.Echo("Exit Code:" & chrSpace & RunCommand) + End If + + If (DebugMode = False) Then + WScript.Quit(RunCommand) + End If \ No newline at end of file