diff --git a/.augment/rules/PowershellScripts.md b/.augment/rules/PowershellScripts.md new file mode 100644 index 0000000..8baedd5 --- /dev/null +++ b/.augment/rules/PowershellScripts.md @@ -0,0 +1,4 @@ +--- +type: "manual" +--- + diff --git a/OSD-ScriptTemplate.exe b/Invoke-OpenSSHConfiguration.exe similarity index 100% rename from OSD-ScriptTemplate.exe rename to Invoke-OpenSSHConfiguration.exe diff --git a/Invoke-OpenSSHConfiguration.ps1 b/Invoke-OpenSSHConfiguration.ps1 new file mode 100644 index 0000000..2ede0fc --- /dev/null +++ b/Invoke-OpenSSHConfiguration.ps1 @@ -0,0 +1,344 @@ +#Requires -Version 5 + +<# + .SYNOPSIS + A short synopsis of of what the script does + + .DESCRIPTION + A meaningful description of what the script 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 + A useful link +#> + +[CmdletBinding(SupportsShouldProcess=$True)] + Param + ( + [Parameter(Mandatory=$False)] + [ValidateNotNullOrEmpty()] + [Alias('TSVars', 'TSVs')] + [String[]]$TaskSequenceVariables, + + [Parameter(Mandatory=$False)] + [ValidateNotNullOrEmpty()] + [Alias('TSVD', 'TSVDL')] + [String[]]$TSVariableDecodeList, + + [Parameter(Mandatory=$False)] + [ValidateNotNullOrEmpty()] + [Alias('LogDir', 'LogPath')] + [System.IO.DirectoryInfo]$LogDirectory, + + [Parameter(Mandatory=$False)] + [Switch]$ContinueOnError + ) + +Function Test-ProcessElevationStatus + { + $Identity = [System.Security.Principal.WindowsIdentity]::GetCurrent() + $Principal = New-Object -TypeName 'System.Security.Principal.WindowsPrincipal' -ArgumentList ($Identity) + $Result = $Principal.IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator) + + Write-Output -InputObject ($Result) + } + +Switch (Test-ProcessElevationStatus) + { + Default + { + Try + { + #region Define Default Action Preferences + $Script:InformationPreference = 'Continue' + $Script:DebugPreference = 'SilentlyContinue' + $Script:ErrorActionPreference = 'Stop' + $Script:VerbosePreference = 'SilentlyContinue' + $Script:WarningPreference = 'Continue' + $Script:ConfirmPreference = 'None' + $Script:WhatIfPreference = $False + #endregion + + #region Set the default exit code for the script (By default, the script will exit with an exit code of 0) + [System.Environment]::ExitCode = 0 + #endregion + + #region Initialize Toolkit (This operation loads functions, modules, and variables into the current session, so if you do not see a variable defined below, it is because it is defined in the Toolkit) + Try + { + [System.IO.FileInfo]$ToolkitScriptPath = "$([System.IO.Path]::GetDirectoryName($MyInvocation.MyCommand.Definition))\Toolkit\Toolkit.ps1" + + . "$($ToolkitScriptPath.FullName)" -CallingScriptInvocationInfo ($MyInvocation) -CallingScriptParameterSetName ($PSCmdlet.ParameterSetName) + } + Catch + { + [System.Environment]::ExitCode = 6000 + + Throw + } + #endregion + + #region Set default parameter values + Switch ($True) + { + {([System.String]::IsNullOrEmpty($ExampleVariable) -eq $True) -or ([System.String]::IsNullOrWhiteSpace($ExampleVariable) -eq $True)} + { + + } + } + #endregion + + #region Perform Script Actions + $WindowsCapabilityInclusionExpression = 'OpenSSH.*' + + $WriteLogMessage.Invoke(0, @("Attempting to retreive the list of Windows capabilities matching expression `"$($WindowsCapabilityInclusionExpression)`". Please Wait...")) + + $WindowsCapabilityList = Get-WindowsCapability -Online -Name ($WindowsCapabilityInclusionExpression) + + $WindowsCapabilityListCount = ($WindowsCapabilityList | Measure-Object).Count + + $WriteLogMessage.Invoke(0, @("Located $($WindowsCapabilityListCount) Windows capabilities matching the specified expression.")) + + Switch ($WindowsCapabilityListCount -gt 0) + { + {($_ -eq $True)} + { + $WindowsCapabilityListCounter = 1 + + For ($WindowsCapabilityListIndex = 0; $WindowsCapabilityListIndex -lt $WindowsCapabilityListCount; $WindowsCapabilityListIndex++) + { + $WindowsCapabilityListItem = $WindowsCapabilityList[$WindowsCapabilityListIndex] + + $WriteLogMessage.Invoke(0, @("Attempting to process windows capability $($WindowsCapabilityListCounter) of $($WindowsCapabilityListCount). Please Wait...")) + + $WriteLogMessage.Invoke(0, @("DisplayName: $($WindowsCapabilityListItem.DisplayName)", "Name: $($WindowsCapabilityListItem.Name)", "State: $($WindowsCapabilityListItem.State)", "Description: $($WindowsCapabilityListItem.Description)")) + + Switch ($WindowsCapabilityListItem.State -iin @('Installed')) + { + {($_ -eq $True)} + { + Try + { + $WriteLogMessage.Invoke(0, @("Attempting to remove windows capability $($WindowsCapabilityListCounter) of $($WindowsCapabilityListCount). Please Wait...")) + + $Null = Remove-WindowsCapability -Online -Name ($WindowsCapabilityListItem.Name) + + $WriteLogMessage.Invoke(0, @("Successfully removed windows capability $($WindowsCapabilityListCounter) of $($WindowsCapabilityListCount). Please Wait...")) + } + Catch + { + $WriteLogMessage.Invoke(2, @("Failed to remove windows capability $($WindowsCapabilityListCounter) of $($WindowsCapabilityListCount).")) + } + Finally + { + + } + } + + Default + { + $WriteLogMessage.Invoke(0, @("Skipping operations on windows capability $($WindowsCapabilityListCounter) of $($WindowsCapabilityListCount). Please Wait...")) + } + } + + $WindowsCapabilityListCounter++ + } + } + } + + $GetInstalledSoftwareList = {Get-InstalledSoftware -FilterExpression {($_.DisplayName -imatch '(.*Open.*SSH.*)') -and ($_.DisplayName -inotmatch '(^.{0,0}$)')} -AllProperties -AllUserProfiles} + + $InstalledSoftwareList = $GetInstalledSoftwareList.Invoke() + + $InstalledSoftwareListCount = ($InstalledSoftwareList | Measure-Object).Count + + Switch ($InstalledSoftwareListCount -eq 0) + { + {($_ -eq $True)} + { + #Determine the OS architecture for asset filtering (Win32, Win64, ARM64) + $OSArchitectureMap = @{ + 'AMD64' = 'Win64' + 'x86' = 'Win32' + 'ARM64' = 'ARM64' + 'ARM' = 'ARM' + } + + $ProcessorArchitecture = [System.Environment]::GetEnvironmentVariable('PROCESSOR_ARCHITECTURE') + + $OSArchitectureFilter = $OSArchitectureMap[$ProcessorArchitecture] + + #Build regex pattern to match MSI for the current architecture (e.g., "OpenSSH-Win64.*\.msi$") + $AssetPattern = "OpenSSH-$($OSArchitectureFilter).*\.msi$" + + $GetGitRepositoryReleaseParameters = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary' + $GetGitRepositoryReleaseParameters.BaseURI = 'https://api.github.com' + $GetGitRepositoryReleaseParameters.RepositoryOwner = "PowerShell" + $GetGitRepositoryReleaseParameters.RepositoryName = "Win32-OpenSSH" + $GetGitRepositoryReleaseParameters.ReleaseTag = "latest" + $GetGitRepositoryReleaseParameters.AssetInclusionExpression = $AssetPattern + $GetGitRepositoryReleaseParameters.DestinationDirectory = [System.IO.Path]::Combine($Env:Windir, 'Temp', $CallingScriptPath.BaseName, 'Downloads') + $GetGitRepositoryReleaseParameters.Download = $True + $GetGitRepositoryReleaseParameters.Force = $False + $GetGitRepositoryReleaseParameters.ContinueOnError = $False + $GetGitRepositoryReleaseParameters.Verbose = $True + + $GetGitRepositoryReleaseResult = Get-GitRepositoryRelease @GetGitRepositoryReleaseParameters + + $GitRepositoryRelease = $GetGitRepositoryReleaseResult | Select-Object -First 1 + + $GitRepositoryReleaseAssetList = $GitRepositoryRelease.ReleaseInfo.AssetList | Where-Object {($_.DestinationPath.Extension -iin @('.msi')) -and ($_.DestinationPath.Exists -eq $True)} + + $GitRepositoryReleaseAssetListCount = ($GitRepositoryReleaseAssetList | Measure-Object).Count + + $GitRepositoryReleaseAssetListCounter = 1 + + For ($GitRepositoryReleaseAssetListIndex = 0; $GitRepositoryReleaseAssetListIndex -lt $GitRepositoryReleaseAssetListCount; $GitRepositoryReleaseAssetListIndex++) + { + $GitRepositoryReleaseAssetListItem = $GitRepositoryReleaseAssetList[$GitRepositoryReleaseAssetListIndex] + + $ExecutionLogPath = [System.IO.FileInfo][System.IO.Path]::Combine($LogDirectory.FullName, "$($GitRepositoryReleaseAssetListItem.DestinationPath.BaseName).log") + + $StartProcessWithOutputParameters = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary' + $StartProcessWithOutputParameters.FilePath = 'msiexec.exe' + #$StartProcessWithOutputParameters.WorkingDirectory = "$($Env:SystemDrive)\Users\Public\Documents" + $StartProcessWithOutputParameters.ArgumentList = New-Object -TypeName 'System.Collections.Generic.List[System.String]' + $StartProcessWithOutputParameters.ArgumentList.Add('/i') + $StartProcessWithOutputParameters.ArgumentList.Add("`"$($GitRepositoryReleaseAssetListItem.DestinationPath.FullName)`"") + $StartProcessWithOutputParameters.ArgumentList.Add('/qn') + $StartProcessWithOutputParameters.ArgumentList.Add('/norestart') + $StartProcessWithOutputParameters.ArgumentList.Add("/l*v `"$($ExecutionLogPath.FullName)`"") + $StartProcessWithOutputParameters.AcceptableExitCodeList = New-Object -TypeName 'System.Collections.Generic.List[System.String]' + $StartProcessWithOutputParameters.AcceptableExitCodeList.Add('0') + $StartProcessWithOutputParameters.AcceptableExitCodeList.Add('3010') + $StartProcessWithOutputParameters.CreateNoWindow = $True + $StartProcessWithOutputParameters.ExecutionTimeout = [System.Timespan]::FromMinutes(15) + $StartProcessWithOutputParameters.ExecutionTimeoutInterval = [System.Timespan]::FromSeconds(5) + $StartProcessWithOutputParameters.SecureArgumentList = $False + $StartProcessWithOutputParameters.LogOutput = $True + $StartProcessWithOutputParameters.ContinueOnError = $False + $StartProcessWithOutputParameters.Verbose = $True + $StartProcessWithOutputParameters.ErrorAction = [System.Management.Automation.Actionpreference]::Stop + + $MSIProductInfo = Get-MSIPropertyList -Path ($GitRepositoryReleaseAssetListItem.DestinationPath.FullName) + + $WriteLogMessage.Invoke(0, @("Attempting to release binary $($GitRepositoryReleaseAssetListCounter) of $($GitRepositoryReleaseAssetListCount). Please Wait...", "Manufacturer: $($MSIProductInfo.Manufacturer)", "ProductName: $($MSIProductInfo.ProductName)", "ProductVersion: $($MSIProductInfo.ProductVersion)", "Product Language: $($MSIProductInfo.ProductLanguage)")) + + $StartProcessWithOutputResult = Start-ProcessWithOutput @StartProcessWithOutputParameters + + $GitRepositoryReleaseAssetListCounter++ + } + } + } + + $InstalledSoftwareList = $GetInstalledSoftwareList.Invoke() + + $InstalledSoftwareListCount = ($InstalledSoftwareList | Measure-Object).Count + + Switch ($InstalledSoftwareListCount -gt 0) + { + {($_ -eq $True)} + { + ###Do configuration stuff here + #PublicKeys - New and from list idepotent + #Certs - From windows store to PEM + #Options etc + + } + } + #endregion + } + Catch + { + #region Perform error handling actions + $ErrorHandlingDefinition.Invoke($Error[0], 2, $ContinueOnError.IsPresent) + #endregion + } + Finally + { + #region Perform finalization actions + $FinalizationActions.Invoke() + #endregion + } + } + + {($_ -eq $False)} + { + [System.IO.FileInfo]$ScriptPath = "$($MyInvocation.MyCommand.Definition)" + + #$CurrentExecutionPolicy = Get-ExecutionPolicy -Scope Process + + $CurrentExecutionPolicy = 'Bypass' + + $ArgumentList = New-Object -TypeName 'System.Collections.Generic.List[String]' + $ArgumentList.Add("-ExecutionPolicy $($CurrentExecutionPolicy)") + $ArgumentList.Add('-NonInteractive') + $ArgumentList.Add('-NoProfile') + $ArgumentList.Add('-NoLogo') + $ArgumentList.Add('-NoExit') + $ArgumentList.Add('-Command') + $ArgumentList.Add("`"`& {. `"$($ScriptPath.FullName)`"") + + $MyInvocation.UnboundArguments.GetEnumerator() | ForEach-Object {$ArgumentList.Add("-$($_.Key) @($($_.Value | ForEach-Object {`"$($_)`"}))")} + + $PSBoundParameters.GetEnumerator() | ForEach-Object {$ArgumentList.Add("-$($_.Key) @($($_.Value | ForEach-Object {`"$($_)`"}))")} + + $ArgumentListTargetIndex = $ArgumentList.Count - 1 + + $ArgumentListIndexItem = $ArgumentList[$ArgumentListTargetIndex] + + $Null = $ArgumentList.RemoveAt($ArgumentListTargetIndex) + + $Null = $ArgumentList.Insert($ArgumentListTargetIndex, ($ArgumentListIndexItem + ';')) + + $ArgumentList.Add("[System.Environment]::Exit((`$LASTEXITCODE -Bor [Int](-Not `$? -And -Not `$LASTEXITCODE)))}`"") + + $ScriptInterpreterList = New-Object -TypeName 'System.Collections.Generic.List[System.String]' + $ScriptInterpreterList.Add('powershell.exe') + $ScriptInterpreterList.Add('pwsh.exe') + + :ScriptInterpreterListLoop ForEach ($ScriptInterpreter In $ScriptInterpreterList) + { + $ScriptInterpreterObject = Try {Get-Command -Name ($ScriptInterpreter) -ErrorAction SilentlyContinue} Catch {$Null} + + Switch ($Null -ine $ScriptInterpreterObject) + { + {($_ -eq $True)} + { + $Null = Start-Process -FilePath ($ScriptInterpreterObject.Path) -WorkingDirectory "$($Env:Temp.TrimEnd('\'))" -ArgumentList ($ArgumentList.ToArray()) -WindowStyle Normal -Verb RunAs -PassThru + + Break ScriptInterpreterListLoop + } + } + } + } + } \ No newline at end of file diff --git a/OSD-ScriptTemplate.ps1 b/OSD-ScriptTemplate.ps1 deleted file mode 100644 index fc86eb2..0000000 --- a/OSD-ScriptTemplate.ps1 +++ /dev/null @@ -1,188 +0,0 @@ -#Requires -Version 5 - -<# - .SYNOPSIS - A short synopsis of of what the script does - - .DESCRIPTION - A meaningful description of what the script 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 - A useful link -#> - -[CmdletBinding(SupportsShouldProcess=$True)] - Param - ( - [Parameter(Mandatory=$False)] - [ValidateNotNullOrEmpty()] - [Alias('TSVars', 'TSVs')] - [String[]]$TaskSequenceVariables, - - [Parameter(Mandatory=$False)] - [ValidateNotNullOrEmpty()] - [Alias('TSVD', 'TSVDL')] - [String[]]$TSVariableDecodeList, - - [Parameter(Mandatory=$False)] - [ValidateNotNullOrEmpty()] - [Alias('LogDir', 'LogPath')] - [System.IO.DirectoryInfo]$LogDirectory, - - [Parameter(Mandatory=$False)] - [Switch]$ContinueOnError - ) - -Function Test-ProcessElevationStatus - { - $Identity = [System.Security.Principal.WindowsIdentity]::GetCurrent() - $Principal = New-Object -TypeName 'System.Security.Principal.WindowsPrincipal' -ArgumentList ($Identity) - $Result = $Principal.IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator) - - Write-Output -InputObject ($Result) - } - -Switch (Test-ProcessElevationStatus) - { - Default - { - Try - { - #region Define Default Action Preferences - $Script:InformationPreference = 'Continue' - $Script:DebugPreference = 'SilentlyContinue' - $Script:ErrorActionPreference = 'Stop' - $Script:VerbosePreference = 'SilentlyContinue' - $Script:WarningPreference = 'Continue' - $Script:ConfirmPreference = 'None' - $Script:WhatIfPreference = $False - #endregion - - #region Set the default exit code for the script (By default, the script will exit with an exit code of 0) - [System.Environment]::ExitCode = 0 - #endregion - - #region Initialize Toolkit (This operation loads functions, modules, and variables into the current session, so if you do not see a variable defined below, it is because it is defined in the Toolkit) - Try - { - [System.IO.FileInfo]$ToolkitScriptPath = "$([System.IO.Path]::GetDirectoryName($MyInvocation.MyCommand.Definition))\Toolkit\Toolkit.ps1" - - . "$($ToolkitScriptPath.FullName)" -CallingScriptInvocationInfo ($MyInvocation) -CallingScriptParameterSetName ($PSCmdlet.ParameterSetName) - } - Catch - { - [System.Environment]::ExitCode = 6000 - - Throw - } - #endregion - - #region Set default parameter values - Switch ($True) - { - {([System.String]::IsNullOrEmpty($ExampleVariable) -eq $True) -or ([System.String]::IsNullOrWhiteSpace($ExampleVariable) -eq $True)} - { - - } - } - #endregion - - #region Perform Script Actions - #Place custom code here - #endregion - } - Catch - { - #region Perform error handling actions - $ErrorHandlingDefinition.Invoke($Error[0], 2, $ContinueOnError.IsPresent) - #endregion - } - Finally - { - #region Perform finalization actions - $FinalizationActions.Invoke() - #endregion - } - } - - {($_ -eq $False)} - { - [System.IO.FileInfo]$ScriptPath = "$($MyInvocation.MyCommand.Definition)" - - #$CurrentExecutionPolicy = Get-ExecutionPolicy -Scope Process - - $CurrentExecutionPolicy = 'Bypass' - - $ArgumentList = New-Object -TypeName 'System.Collections.Generic.List[String]' - $ArgumentList.Add("-ExecutionPolicy $($CurrentExecutionPolicy)") - $ArgumentList.Add('-NonInteractive') - $ArgumentList.Add('-NoProfile') - $ArgumentList.Add('-NoLogo') - $ArgumentList.Add('-NoExit') - $ArgumentList.Add('-Command') - $ArgumentList.Add("`"`& {. `"$($ScriptPath.FullName)`"") - - $MyInvocation.UnboundArguments.GetEnumerator() | ForEach-Object {$ArgumentList.Add("-$($_.Key) @($($_.Value | ForEach-Object {`"$($_)`"}))")} - - $PSBoundParameters.GetEnumerator() | ForEach-Object {$ArgumentList.Add("-$($_.Key) @($($_.Value | ForEach-Object {`"$($_)`"}))")} - - $ArgumentListTargetIndex = $ArgumentList.Count - 1 - - $ArgumentListIndexItem = $ArgumentList[$ArgumentListTargetIndex] - - $Null = $ArgumentList.RemoveAt($ArgumentListTargetIndex) - - $Null = $ArgumentList.Insert($ArgumentListTargetIndex, ($ArgumentListIndexItem + ';')) - - $ArgumentList.Add("[System.Environment]::Exit((`$LASTEXITCODE -Bor [Int](-Not `$? -And -Not `$LASTEXITCODE)))}`"") - - $ScriptInterpreterList = New-Object -TypeName 'System.Collections.Generic.List[System.String]' - $ScriptInterpreterList.Add('powershell.exe') - $ScriptInterpreterList.Add('pwsh.exe') - - :ScriptInterpreterListLoop ForEach ($ScriptInterpreter In $ScriptInterpreterList) - { - $ScriptInterpreterObject = Try {Get-Command -Name ($ScriptInterpreter) -ErrorAction SilentlyContinue} Catch {$Null} - - Switch ($Null -ine $ScriptInterpreterObject) - { - {($_ -eq $True)} - { - $Null = Start-Process -FilePath ($ScriptInterpreterObject.Path) -WorkingDirectory "$($Env:Temp.TrimEnd('\'))" -ArgumentList ($ArgumentList.ToArray()) -WindowStyle Normal -Verb RunAs -PassThru - - Break ScriptInterpreterListLoop - } - } - } - } - } \ No newline at end of file diff --git a/Toolkit/Functions/ConvertFrom-CertificateToPEM.ps1 b/Toolkit/Functions/ConvertFrom-CertificateToPEM.ps1 new file mode 100644 index 0000000..7d83dca --- /dev/null +++ b/Toolkit/Functions/ConvertFrom-CertificateToPEM.ps1 @@ -0,0 +1,603 @@ +## Microsoft Function Naming Convention: http://msdn.microsoft.com/en-us/library/ms714428(v=vs.85).aspx + +#region Function ConvertFrom-CertificateToPEM +Function ConvertFrom-CertificateToPEM + { + <# + .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 + + .EXAMPLE + $ConvertFromCertificateToPEMParameters = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary' + $ConvertFromCertificateToPEMParameters.SerialNumberList = New-Object -TypeName 'System.Collections.Generic.List[System.String]' + $ConvertFromCertificateToPEMParameters.SerialNumberList.Add('3d05e1f73beb5d3cd1be2b9f8d96f6c323260f78') + $ConvertFromCertificateToPEMParameters.Export = $True + $ConvertFromCertificateToPEMParameters.ExportRootDirectory = "$($Env:Userprofile)\Downloads\Certificates" + $ConvertFromCertificateToPEMParameters.ContinueOnError = $False + $ConvertFromCertificateToPEMParameters.Verbose = $True + + $ConvertFromCertificateToPEMResult = ConvertFrom-CertificateToPEM @ConvertFromCertificateToPEMParameters + + Write-Output -InputObject ($ConvertFromCertificateToPEMResult) + + .EXAMPLE + [String]$RootCALocation = 'Cert:\LocalMachine\My' + + [String]$RootCAFriendlyName = 'Self Signed Root CA Certificate' + + $DNSHostEntry = [System.Net.Dns]::GetHostEntry('LocalHost') + + $NewSelfSignedCertificateParameters = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary' + $NewSelfSignedCertificateParameters.FriendlyName = $RootCAFriendlyName + $NewSelfSignedCertificateParameters.Subject = "$($Env:ComputerName.ToUpper())" + $NewSelfSignedCertificateParameters.DnsName = New-Object -TypeName 'System.Collections.Generic.List[System.String]' + $NewSelfSignedCertificateParameters.DnsName.Add($DNSHostEntry.HostName) + $NewSelfSignedCertificateParameters.NotBefore = (Get-Date).Date + $NewSelfSignedCertificateParameters.NotAfter = $NewSelfSignedCertificateParameters.NotBefore.Date.AddYears(10) + $NewSelfSignedCertificateParameters.CertStoreLocation = $RootCALocation + $NewSelfSignedCertificateParameters.KeyExportPolicy = New-Object -TypeName 'System.Collections.Generic.List[Microsoft.Certificateservices.Commands.Keyexportpolicy]' + $NewSelfSignedCertificateParameters.KeyExportPolicy.Add('Exportable') + $NewSelfSignedCertificateParameters.Provider = 'Microsoft Enhanced RSA and AES Cryptographic Provider' + $NewSelfSignedCertificateParameters.TextExtension = New-Object -TypeName 'System.Collections.Generic.List[System.String]' + $NewSelfSignedCertificateParameters.TextExtension.Add('2.5.29.19={text}CA=1&pathlength=3') + $NewSelfSignedCertificateParameters.TextExtension.Add('2.5.29.37={text}1.3.6.1.5.5.7.3.3') + $NewSelfSignedCertificateParameters.KeyUsageProperty = New-Object -TypeName 'System.Collections.Generic.List[Microsoft.Certificateservices.Commands.Keyusageproperty]' + $NewSelfSignedCertificateParameters.KeyUsageProperty.Add('All') + $NewSelfSignedCertificateParameters.KeyUsage = New-Object -TypeName 'System.Collections.Generic.List[Microsoft.Certificateservices.Commands.Keyusage]' + $NewSelfSignedCertificateParameters.KeyUsage.Add('CertSign') + $NewSelfSignedCertificateParameters.KeyUsage.Add('CRLSign') + $NewSelfSignedCertificateParameters.KeyUsage.Add('DigitalSignature') + $NewSelfSignedCertificateParameters.Confirm = $False + + $SelfSignedCertificate = New-SelfSignedCertificate @NewSelfSignedCertificateParameters + + $ConvertFromCertificateToPEMParameters = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary' + $ConvertFromCertificateToPEMParameters.CertificateList = New-Object -TypeName 'System.Collections.Generic.List[System.Security.Cryptography.X509Certificates.X509Certificate2]' + $ConvertFromCertificateToPEMParameters.CertificateList.Add($SelfSignedCertificate) + $ConvertFromCertificateToPEMParameters.Export = $True + $ConvertFromCertificateToPEMParameters.ExportRootDirectory = "$($Env:Userprofile)\Downloads\Certificates" + $ConvertFromCertificateToPEMParameters.ContinueOnError = $False + $ConvertFromCertificateToPEMParameters.Verbose = $True + + $ConvertFromCertificateToPEMResult = ConvertFrom-CertificateToPEM @ConvertFromCertificateToPEMParameters + + Write-Output -InputObject ($ConvertFromCertificateToPEMResult) + + .NOTES + Any useful tidbits + + .LINK + Place any useful link here where your function or cmdlet can be referenced + #> + + [CmdletBinding(ConfirmImpact = 'Low', DefaultParameterSetName = 'BySerialNumber')] + + Param + ( + [Parameter(Mandatory=$True, ParameterSetName = 'BySerialNumber')] + [ValidateNotNullOrEmpty()] + [String[]]$SerialNumberList, + + [Parameter(Mandatory=$True, ParameterSetName = 'ByCertificate')] + [ValidateNotNullOrEmpty()] + [System.Security.Cryptography.X509Certificates.X509Certificate2[]]$CertificateList, + + [Parameter(Mandatory=$False)] + [Switch]$Export, + + [Parameter(Mandatory=$False)] + [AllowEmptyString()] + [AllowNull()] + [System.IO.DirectoryInfo]$ExportRootDirectory, + + [Parameter(Mandatory=$False)] + [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 = { + Param + ( + [Int16]$Severity, + [Boolean]$ContinueOnError + ) + + $ExceptionPropertyDictionary = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary' + $ExceptionPropertyDictionary.Message = $_.Exception.Message + $ExceptionPropertyDictionary.Category = $_.Exception.ErrorRecord.FullyQualifiedErrorID + $ExceptionPropertyDictionary.Script = [System.IO.Path]::GetFileName($_.InvocationInfo.ScriptName) + $ExceptionPropertyDictionary.LineNumber = $_.InvocationInfo.ScriptLineNumber + $ExceptionPropertyDictionary.LinePosition = $_.InvocationInfo.OffsetInLine + $ExceptionPropertyDictionary.Code = $_.InvocationInfo.Line.Trim() + + $ExceptionMessageList = New-Object -TypeName 'System.Collections.Generic.List[String]' + + ForEach ($ExceptionProperty In $ExceptionPropertyDictionary.GetEnumerator()) + { + $ExceptionMessageList.Add("[$($ExceptionProperty.Key): $($ExceptionProperty.Value)]") + } + + $LogMessageParameters = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary' + $LogMessageParameters.Message = $ExceptionMessageList -Join ' ' + $LogMessageParameters.Verbose = $True + + Switch ($Severity) + { + {($_ -in @(1))} {Write-Verbose @LogMessageParameters} + {($_ -in @(2))} {Write-Warning @LogMessageParameters} + {($_ -in @(3))} {Write-Error @LogMessageParameters} + } + + Switch ($ContinueOnError) + { + {($_ -eq $False)} + { + 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 {Try {"-$($_.Key):$($_.Value.GetType().Name)"} Catch {"-$($_.Key):Unknown"}} + $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 + + #Create an object that will contain the functions output. + $OutputObjectList = New-Object -TypeName 'System.Collections.Generic.List[System.Management.Automation.PSObject]' + + #Set default parameter value(s) + Switch ($True) + { + {([String]::IsNullOrEmpty($ExportRootDirectory) -eq $True) -or ([String]::IsNullOrWhiteSpace($ExportRootDirectory) -eq $True)} + { + [System.IO.DirectoryInfo]$ExportRootDirectory = "$($Env:Public)\Documents\$($FunctionName)" + } + } + + $X509Certificate2List = New-Object -TypeName 'System.Collections.Generic.List[System.Security.Cryptography.X509Certificates.X509Certificate2]' + + Switch ($PSCmdlet.ParameterSetName) + { + {($_ -iin @('BySerialNumber'))} + { + $ExistingCertificateList = Get-ChildItem -Path @('Microsoft.PowerShell.Security\Certificate::\') -Recurse -Force -ErrorAction SilentlyContinue | Where-Object {($Null -ine $_.SerialNumber)} + + ForEach ($SerialNumber In $SerialNumberList) + { + Switch ($ExistingCertificateList.SerialNumber -icontains $SerialNumber) + { + {($_ -eq $True)} + { + $ExistingCertificates = $ExistingCertificateList | Where-Object {($_.SerialNumber -ieq $SerialNumber)} | Sort-Object -Property @('NotAfter') -Descending -Unique + + $ExistingCertificateCount = ($ExistingCertificates | Measure-Object).Count + + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Located $($ExistingCertificateCount) certificate(s) for certificate serial number `"$($SerialNumber)`"." + Write-Verbose -Message ($LoggingDetails.LogMessage) + + Switch ($ExistingCertificateCount) + { + {($_ -eq 1)} + { + $X509Certificate2List.Add($ExistingCertificates) + } + + {($_ -gt 1)} + { + ForEach ($ExistingCertificate In $ExistingCertificates) + { + $X509Certificate2List.Add($ExistingCertificate) + } + } + } + } + } + } + } + + {($_ -iin @('ByCertificate'))} + { + $CertificateList | Sort-Object -Property @('NotAfter') -Descending -Unique | ForEach-Object {($X509Certificate2List.Add($_))} + } + } + } + Catch + { + $ErrorHandlingDefinition.Invoke(2, $ContinueOnError.IsPresent) + } + Finally + { + + } + } + + Process + { + Try + { + $X509Certificate2ListCounter = 1 + + $X509Certificate2ListCount = ($X509Certificate2List | Measure-Object).Count + + For ($X509Certificate2ListIndex = 0; $X509Certificate2ListIndex -lt $X509Certificate2ListCount; $X509Certificate2ListIndex++) + { + Try + { + $X509Certificate2ListItem = $X509Certificate2List[$X509Certificate2ListIndex] + + $OutputObjectProperties = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary' + $OutputObjectProperties.SerialNumber = $X509Certificate2ListItem.SerialNumber + $OutputObjectProperties.Blobs = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary' + $OutputObjectProperties.Blobs.Certificate = $Null + $OutputObjectProperties.Blobs.PublicKey = $Null + $OutputObjectProperties.Blobs.PrivateKey = $Null + $OutputObjectProperties.Exports = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary' + $OutputObjectProperties.Exports.Certificate = $Null + $OutputObjectProperties.Exports.PublicKey = $Null + $OutputObjectProperties.Exports.PrivateKey = $Null + $OutputObjectProperties.Certificate = $X509Certificate2ListItem + + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Attempting to process certificate `"$($X509Certificate2ListItem.SerialNumber)`". Please Wait..." + Write-Verbose -Message ($LoggingDetails.LogMessage) + + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Issuer: $($X509Certificate2ListItem.Issuer)" + Write-Verbose -Message ($LoggingDetails.LogMessage) + + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Thumbprint: $($X509Certificate2ListItem.Thumbprint)" + Write-Verbose -Message ($LoggingDetails.LogMessage) + + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Serial Number: $($X509Certificate2ListItem.SerialNumber)" + Write-Verbose -Message ($LoggingDetails.LogMessage) + + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - DNS Name List: $($X509Certificate2ListItem.DnsNameList -Join ', ')" + Write-Verbose -Message ($LoggingDetails.LogMessage) + + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Not Before: $($X509Certificate2ListItem.NotBefore.ToString($DateTimeLogFormat))" + Write-Verbose -Message ($LoggingDetails.LogMessage) + + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Not After: $($X509Certificate2ListItem.NotAfter.ToString($DateTimeLogFormat))" + Write-Verbose -Message ($LoggingDetails.LogMessage) + + $StringBuilders = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary' + $StringBuilders.Certificate = New-Object -TypeName 'System.Text.StringBuilder' + $StringBuilders.PrivateKey = New-Object -TypeName 'System.Text.StringBuilder' + $StringBuilders.PublicKey = New-Object -TypeName 'System.Text.StringBuilder' + + $X509KeyStorageFlags = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary' + $X509KeyStorageFlags.Valid = [System.Enum]::GetNames('System.Security.Cryptography.X509Certificates.X509KeyStorageFlags') | Sort-Object + $X509KeyStorageFlags.Specified = New-Object -TypeName 'System.Collections.Generic.List[System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]' + $X509KeyStorageFlags.Specified.Add('Exportable') + + $X509Certificate2 = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary' + $X509Certificate2.Object = $X509Certificate2ListItem + $X509Certificate2.ObjectEncoded = $Null + $X509Certificate2.ContentType = $Null + $X509Certificate2.OID = $Null + $X509Certificate2.DSA = $Null + $X509Certificate2.CNG = $Null + $X509Certificate2.PublicKey = $Null + $X509Certificate2.PublicKeyEncoded = $Null + $X509Certificate2.PrivateKey = $Null + $X509Certificate2.PrivateKeyEncoded = $Null + + $X509Certificate2.ContentType = [System.Security.Cryptography.X509Certificates.X509Certificate2]::GetCertContentType($X509Certificate2.Object.RawData) + + $X509Certificate2.ObjectEncoded = [System.Convert]::ToBase64String($X509Certificate2.Object.RawData, [System.Base64FormattingOptions]::InsertLineBreaks) + + $Null = $StringBuilders.Certificate.Append('-----BEGIN CERTIFICATE-----').AppendLine() + $Null = $StringBuilders.Certificate.Append($X509Certificate2.ObjectEncoded).AppendLine() + $Null = $StringBuilders.Certificate.Append('-----END CERTIFICATE-----') + + $OutputObjectProperties.Blobs.Certificate = $StringBuilders.Certificate.ToString() + + $X509Certificate2.OID = [System.Security.Cryptography.OID]::New($X509Certificate2.Object.GetKeyAlgorithm()) + + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - A certificate key algorithm of `"$($X509Certificate2.OID.FriendlyName)`" [OID: $($X509Certificate2.OID.Value)] was detected." + Write-Verbose -Message ($LoggingDetails.LogMessage) + + Switch ($Null -ine $X509Certificate2.Object.PublicKey) + { + {($_ -eq $True)} + { + Switch ($X509Certificate2.OID.FriendlyName) + { + {($_ -iin @('RSA'))} + { + $X509Certificate2.DSA = [System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPublicKey($X509Certificate2.Object) + $X509Certificate2.CNG = [System.Security.Cryptography.RSACNG]($X509Certificate2.DSA) + } + + {($_ -iin @('ECC'))} + { + $X509Certificate2.DSA = [System.Security.Cryptography.X509Certificates.ECDSACertificateExtensions]::GetECDSAPublicKey($X509Certificate2.Object) + $X509Certificate2.CNG = [System.Security.Cryptography.ECDSACNG]($X509Certificate2.DSA) + } + + Default + { + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - The certificate key algorithm of `"$($X509Certificate2.OID.FriendlyName)`" [OID: $($X509Certificate2.OID.Value)] is unsupported for public key extraction." + Write-Warning -Message ($LoggingDetails.LogMessage) + } + } + + $X509Certificate2.PublicKey = $X509Certificate2.CNG.Key + + $X509Certificate2.PublicKeyEncoded = [System.Convert]::ToBase64String(($X509Certificate2.PublicKey.Export([System.Security.Cryptography.CngKeyBlobFormat]::GenericPublicBlob)), [System.Base64FormattingOptions]::InsertLineBreaks) + + $Null = $StringBuilders.PublicKey.Append('-----BEGIN PUBLIC KEY-----').AppendLine() + $Null = $StringBuilders.PublicKey.Append($X509Certificate2.PublicKeyEncoded).AppendLine() + $Null = $StringBuilders.PublicKey.Append('-----END PUBLIC KEY-----') + + $OutputObjectProperties.Blobs.PublicKey = $StringBuilders.PublicKey.ToString() + } + + Default + { + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - The certificate `"$($X509Certificate2ListItem.SerialNumber)`" [Thumbprint: $($X509Certificate2ListItem.Thumbprint)] does not contain a public key." + Write-Warning -Message ($LoggingDetails.LogMessage) + } + } + + Switch ($X509Certificate2.Object.HasPrivateKey) + { + {($_ -eq $True)} + { + Switch ($X509Certificate2.OID.FriendlyName) + { + {($_ -iin @('RSA'))} + { + $X509Certificate2.DSA = [System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($X509Certificate2.Object) + $X509Certificate2.CNG = [System.Security.Cryptography.RSACNG]($X509Certificate2.DSA) + } + + {($_ -iin @('ECC'))} + { + $X509Certificate2.DSA = [System.Security.Cryptography.X509Certificates.ECDsaCertificateExtensions]::GetECDSAPrivateKey($X509Certificate2.Object) + $X509Certificate2.CNG = [System.Security.Cryptography.ECDSACNG]($X509Certificate2.DSA) + } + + Default + { + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - The certificate key algorithm of `"$($X509Certificate2.OID.FriendlyName)`" [OID: $($X509Certificate2.OID.Value)] is unsupported for private key extracted." + Write-Warning -Message ($LoggingDetails.LogMessage) + } + } + + $X509Certificate2.PrivateKey = $X509Certificate2.CNG.Key + + $X509Certificate2.PrivateKeyEncoded = [System.Convert]::ToBase64String(($X509Certificate2.PrivateKey.Export([System.Security.Cryptography.CngKeyBlobFormat]::PKCS8PrivateBlob)), [System.Base64FormattingOptions]::InsertLineBreaks) + + $Null = $StringBuilders.PrivateKey.Append('-----BEGIN PRIVATE KEY-----').AppendLine() + $Null = $StringBuilders.PrivateKey.Append($X509Certificate2.PrivateKeyEncoded).AppendLine() + $Null = $StringBuilders.PrivateKey.Append('-----END PRIVATE KEY-----') + + $OutputObjectProperties.Blobs.PrivateKey = $StringBuilders.PrivateKey.ToString() + } + + Default + { + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - The certificate `"$($X509Certificate2ListItem.SerialNumber)`" [Thumbprint: $($X509Certificate2ListItem.Thumbprint)] does not contain a private key. Please ensure that the private key is exportable." + Write-Warning -Message ($LoggingDetails.LogMessage) + } + } + + $WriteAllTextParameters = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary' + $WriteAllTextParameters.Path = $Null + $WriteAllTextParameters.Contents = $Null + $WriteAllTextParameters.Encoding = [System.Text.Encoding]::ASCII + + Switch ($Export.IsPresent) + { + {($_ -eq $True)} + { + $X509Certificate2.ExportDirectory = "$($ExportRootDirectory.FullName)\$($X509Certificate2ListItem.SerialNumber)" -As [System.IO.DirectoryInfo] + + Switch (([String]::IsNullOrEmpty($OutputObjectProperties.Blobs.Certificate) -eq $False) -or ([String]::IsNullOrWhiteSpace($OutputObjectProperties.Blobs.Certificate) -eq $False)) + { + {($_ -eq $True)} + { + Switch ($True) + { + {([System.IO.Directory]::Exists($X509Certificate2.ExportDirectory.FullName) -eq $False)} + { + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Attempting to create directory `"$($X509Certificate2.ExportDirectory.FullName)`". Please Wait..." + Write-Verbose -Message ($LoggingDetails.LogMessage) + + $Null = [System.IO.Directory]::CreateDirectory($X509Certificate2.ExportDirectory.FullName) + } + } + + $WriteAllTextParameters.Path = "$($X509Certificate2.ExportDirectory.FullName)\Certificate.pem" + $WriteAllTextParameters.Contents = $OutputObjectProperties.Blobs.Certificate + + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Attempting to export the certificate to `"$($WriteAllTextParameters.Path)`". Please Wait..." + Write-Verbose -Message ($LoggingDetails.LogMessage) + + $Null = [System.IO.File]::WriteAllText($WriteAllTextParameters.Path, $WriteAllTextParameters.Contents, $WriteAllTextParameters.Encoding) + + Switch ($?) + { + {($_ -eq $True)} + { + $OutputObjectProperties.Exports.Certificate = $WriteAllTextParameters.Path + } + } + } + + Default + { + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Unable to export the certificate from `"$($X509Certificate2ListItem.SerialNumber)`" [Thumbprint: $($X509Certificate2ListItem.Thumbprint)] to `"$($WriteAllTextParameters.Path)`"." + Write-Warning -Message ($LoggingDetails.LogMessage) + } + } + + Switch (([String]::IsNullOrEmpty($OutputObjectProperties.Blobs.PublicKey) -eq $False) -or ([String]::IsNullOrWhiteSpace($OutputObjectProperties.Blobs.PublicKey) -eq $False)) + { + {($_ -eq $True)} + { + $WriteAllTextParameters.Path = "$($X509Certificate2.ExportDirectory.FullName)\PublicKey.pem" + $WriteAllTextParameters.Contents = $OutputObjectProperties.Blobs.PublicKey + + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Attempting to export the public key to `"$($WriteAllTextParameters.Path)`". Please Wait..." + Write-Verbose -Message ($LoggingDetails.LogMessage) + + $Null = [System.IO.File]::WriteAllText($WriteAllTextParameters.Path, $WriteAllTextParameters.Contents, $WriteAllTextParameters.Encoding) + + Switch ($?) + { + {($_ -eq $True)} + { + $OutputObjectProperties.Exports.PublicKey = $WriteAllTextParameters.Path + } + } + } + + Default + { + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Unable to export the public key from certificate `"$($X509Certificate2ListItem.SerialNumber)`" [Thumbprint: $($X509Certificate2ListItem.Thumbprint)] to `"$($WriteAllTextParameters.Path)`"." + Write-Warning -Message ($LoggingDetails.LogMessage) + } + } + + Switch (([String]::IsNullOrEmpty($OutputObjectProperties.Blobs.PrivateKey) -eq $False) -or ([String]::IsNullOrWhiteSpace($OutputObjectProperties.Blobs.PrivateKey) -eq $False)) + { + {($_ -eq $True)} + { + $WriteAllTextParameters.Path = "$($X509Certificate2.ExportDirectory.FullName)\PrivateKey.pem" + $WriteAllTextParameters.Contents = $OutputObjectProperties.Blobs.PrivateKey + + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Attempting to export the private key to `"$($WriteAllTextParameters.Path)`". Please Wait..." + Write-Verbose -Message ($LoggingDetails.LogMessage) + + $Null = [System.IO.File]::WriteAllText($WriteAllTextParameters.Path, $WriteAllTextParameters.Contents, $WriteAllTextParameters.Encoding) + + Switch ($?) + { + {($_ -eq $True)} + { + $OutputObjectProperties.Exports.PrivateKey = $WriteAllTextParameters.Path + } + } + } + + Default + { + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Unable to export the private key from certificate `"$($X509Certificate2ListItem.SerialNumber)`" [Thumbprint: $($X509Certificate2ListItem.Thumbprint)] to `"$($WriteAllTextParameters.Path)`"." + Write-Warning -Message ($LoggingDetails.LogMessage) + } + } + } + } + } + Catch + { + $ErrorHandlingDefinition.Invoke(2, $True) + } + Finally + { + $OutputObject = New-Object -TypeName 'System.Management.Automation.PSObject' -Property ($OutputObjectProperties) + + $OutputObjectList.Add($OutputObject) + + $X509Certificate2ListCounter++ + } + } + } + Catch + { + $ErrorHandlingDefinition.Invoke(2, $ContinueOnError.IsPresent) + } + Finally + { + + } + } + + 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(2, $ContinueOnError.IsPresent) + } + 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/Toolkit/Functions/Get-GitRepositoryRelease.ps1 b/Toolkit/Functions/Get-GitRepositoryRelease.ps1 new file mode 100644 index 0000000..171570a --- /dev/null +++ b/Toolkit/Functions/Get-GitRepositoryRelease.ps1 @@ -0,0 +1,738 @@ +## Microsoft Function Naming Convention: http://msdn.microsoft.com/en-us/library/ms714428(v=vs.85).aspx + +#region Function Get-GitRepositoryRelease +Function Get-GitRepositoryRelease + { + <# + .SYNOPSIS + Download release assets from public and private Git repositories. + + .DESCRIPTION + Leverages the GitHub API (by default) to list and download release assets from public and private Git repositories. + Supports filtering by release version (latest, specific tag, or pre-release) and asset name patterns. + The function can download assets while maintaining logging through the $WriteLogMessage scriptblock from the toolkit. + + .PARAMETER AccessToken + A personal access token (PAT) for authenticating with the Git API. Required for private repositories. + For GitHub, generate tokens at https://github.com/settings/tokens with appropriate repository access scopes. + + .PARAMETER BaseURI + The base URI of the Git hosting API. Defaults to 'https://api.github.com' if not specified. + Can be customized for GitHub Enterprise, Gitea, GitLab, or other compatible Git hosting platforms. + + .PARAMETER RepositoryOwner + The username or organization name that owns the repository. This is the first part of the repository URL path. + + .PARAMETER RepositoryName + The name of the repository to access. This is the second part of the repository URL path. + + .PARAMETER ReleaseTag + The specific release tag to download. If not specified, defaults to 'latest'. + Use 'latest' for the most recent non-prerelease version, or specify a tag name like 'v1.0.0'. + + .PARAMETER IncludePreRelease + When specified, includes pre-release versions when searching for releases. + When combined with ReleaseTag 'latest', returns the most recent release including pre-releases. + + .PARAMETER AssetInclusionExpression + A regular expression pattern to include only matching asset file names. + Defaults to '(.*)' to include all assets. Example: '\.exe$' for only .exe files. + + .PARAMETER AssetExclusionExpression + A regular expression pattern to exclude matching asset file names. + Defaults to '^$' to exclude nothing (only matches empty strings). Example: '\.sig$|\.sha256$' to exclude signature files. + + .PARAMETER DestinationDirectory + The local directory path where assets will be downloaded. + Defaults to '$Env:Windir\Temp\GitHubReleases\$RepositoryName' if not specified. + + .PARAMETER Download + When specified, downloads the assets to the DestinationDirectory. Without this switch, only a listing is returned. + + .PARAMETER Force + When specified, overwrites existing files at the destination. Without this switch, existing files are skipped. + + .PARAMETER ContinueOnError + When specified, continues processing remaining items if an error occurs. Without this switch, errors are terminating. + + .EXAMPLE + Download the latest Win32-OpenSSH MSI release, dynamically filtering for the current OS architecture. + + # Determine the OS architecture for asset filtering (Win32, Win64, ARM64) + $OSArchitectureMap = @{ + 'AMD64' = 'Win64' + 'x86' = 'Win32' + 'ARM64' = 'ARM64' + 'ARM' = 'ARM' + } + $ProcessorArchitecture = [System.Environment]::GetEnvironmentVariable('PROCESSOR_ARCHITECTURE') + $OSArchitectureFilter = $OSArchitectureMap[$ProcessorArchitecture] + + # Build regex pattern to match MSI for the current architecture (e.g., "OpenSSH-Win64.*\.msi$") + $AssetPattern = "OpenSSH-$($OSArchitectureFilter).*\.msi$" + + $GetGitRepositoryReleaseParameters = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary' + $GetGitRepositoryReleaseParameters.BaseURI = 'https://api.github.com' + $GetGitRepositoryReleaseParameters.RepositoryOwner = "PowerShell" + $GetGitRepositoryReleaseParameters.RepositoryName = "Win32-OpenSSH" + $GetGitRepositoryReleaseParameters.ReleaseTag = "latest" + $GetGitRepositoryReleaseParameters.AssetInclusionExpression = $AssetPattern + $GetGitRepositoryReleaseParameters.DestinationDirectory = "$($Env:Userprofile)\Downloads\OpenSSH-Release" + $GetGitRepositoryReleaseParameters.Download = $True + $GetGitRepositoryReleaseParameters.Force = $False + $GetGitRepositoryReleaseParameters.ContinueOnError = $False + $GetGitRepositoryReleaseParameters.Verbose = $True + + $GetGitRepositoryReleaseResult = Get-GitRepositoryRelease @GetGitRepositoryReleaseParameters + + Write-Output -InputObject ($GetGitRepositoryReleaseResult) + + .EXAMPLE + Download a specific release version from the microsoft/winget-cli repository. + + $GetGitRepositoryReleaseParameters = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary' + $GetGitRepositoryReleaseParameters.BaseURI = 'https://api.github.com' + $GetGitRepositoryReleaseParameters.RepositoryOwner = "microsoft" + $GetGitRepositoryReleaseParameters.RepositoryName = "winget-cli" + $GetGitRepositoryReleaseParameters.ReleaseTag = "v1.7.10661" + $GetGitRepositoryReleaseParameters.AssetInclusionExpression = '\.msixbundle$' + $GetGitRepositoryReleaseParameters.DestinationDirectory = "$($Env:Userprofile)\Downloads\WinGet-Release" + $GetGitRepositoryReleaseParameters.Download = $True + $GetGitRepositoryReleaseParameters.Force = $False + $GetGitRepositoryReleaseParameters.ContinueOnError = $False + $GetGitRepositoryReleaseParameters.Verbose = $True + + $GetGitRepositoryReleaseResult = Get-GitRepositoryRelease @GetGitRepositoryReleaseParameters + + Write-Output -InputObject ($GetGitRepositoryReleaseResult) + + .EXAMPLE + Download the latest pre-release from a private repository using an access token. + + $GetGitRepositoryReleaseParameters = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary' + $GetGitRepositoryReleaseParameters.AccessToken = "YourAccessToken" + $GetGitRepositoryReleaseParameters.BaseURI = 'https://api.github.com' + $GetGitRepositoryReleaseParameters.RepositoryOwner = "YourOrganization" + $GetGitRepositoryReleaseParameters.RepositoryName = "YourPrivateRepo" + $GetGitRepositoryReleaseParameters.ReleaseTag = "latest" + $GetGitRepositoryReleaseParameters.IncludePreRelease = $True + $GetGitRepositoryReleaseParameters.DestinationDirectory = "$($Env:Userprofile)\Downloads\PrivateRelease" + $GetGitRepositoryReleaseParameters.Download = $True + $GetGitRepositoryReleaseParameters.Force = $True + $GetGitRepositoryReleaseParameters.ContinueOnError = $False + $GetGitRepositoryReleaseParameters.Verbose = $True + + $GetGitRepositoryReleaseResult = Get-GitRepositoryRelease @GetGitRepositoryReleaseParameters + + Write-Output -InputObject ($GetGitRepositoryReleaseResult) + + .NOTES + This function uses the $WriteLogMessage scriptblock from the toolkit for logging. + Ensure the toolkit is dot-sourced before calling this function for proper logging support. + + This function could also theoretically support Git repositories outside of GitHub, such as Gitea, or GitLab, + provided they have compatible API endpoints. + + .LINK + https://docs.github.com/en/rest/releases/releases + + .LINK + https://docs.github.com/en/rest/releases/assets + #> + + [CmdletBinding(ConfirmImpact = 'Low')] + Param + ( + [Parameter(Mandatory=$False)] + [ValidateNotNullOrEmpty()] + [System.String]$AccessToken, + + [Parameter(Mandatory=$False)] + [ValidateNotNullOrEmpty()] + [System.URI]$BaseURI, + + [Parameter(Mandatory=$True)] + [ValidateNotNullOrEmpty()] + [System.String]$RepositoryOwner, + + [Parameter(Mandatory=$True)] + [ValidateNotNullOrEmpty()] + [System.String]$RepositoryName, + + [Parameter(Mandatory=$False)] + [ValidateNotNullOrEmpty()] + [System.String]$ReleaseTag, + + [Parameter(Mandatory=$False)] + [Switch]$IncludePreRelease, + + [Parameter(Mandatory=$False)] + [ValidateNotNullOrEmpty()] + [Regex]$AssetInclusionExpression, + + [Parameter(Mandatory=$False)] + [ValidateNotNullOrEmpty()] + [Regex]$AssetExclusionExpression, + + [Parameter(Mandatory=$False)] + [ValidateNotNullOrEmpty()] + [System.IO.DirectoryInfo]$DestinationDirectory, + + [Parameter(Mandatory=$False)] + [Switch]$Download, + + [Parameter(Mandatory=$False)] + [Switch]$Force, + + [Parameter(Mandatory=$False)] + [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 + $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 = { + Param + ( + [Int16]$Severity, + [Boolean]$ContinueOnError + ) + + $ExceptionPropertyDictionary = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary' + $ExceptionPropertyDictionary.Message = $_.Exception.Message + $ExceptionPropertyDictionary.Category = $_.Exception.ErrorRecord.FullyQualifiedErrorID + $ExceptionPropertyDictionary.Script = Try {[System.IO.Path]::GetFileName($_.InvocationInfo.ScriptName)} Catch {$Null} + $ExceptionPropertyDictionary.LineNumber = $_.InvocationInfo.ScriptLineNumber + $ExceptionPropertyDictionary.LinePosition = $_.InvocationInfo.OffsetInLine + $ExceptionPropertyDictionary.Code = $_.InvocationInfo.Line.Trim() + + $ExceptionMessageList = New-Object -TypeName 'System.Collections.Generic.List[String]' + + ForEach ($ExceptionProperty In $ExceptionPropertyDictionary.GetEnumerator()) + { + Switch ($Null -ine $ExceptionProperty.Value) + { + {($_ -eq $True)} + { + $ExceptionMessageList.Add("[$($ExceptionProperty.Key): $($ExceptionProperty.Value)]") + } + } + } + + $LogMessageParameters = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary' + $LogMessageParameters.Message = $ExceptionMessageList -Join ' ' + $LogMessageParameters.Verbose = $True + + Switch ($Severity) + { + {($_ -in @(1))} {$WriteLogMessage.Invoke(1, @($LogMessageParameters.Message))} + {($_ -in @(2))} {$WriteLogMessage.Invoke(2, @($LogMessageParameters.Message))} + {($_ -in @(3))} {$WriteLogMessage.Invoke(3, @($LogMessageParameters.Message))} + } + + Switch ($ContinueOnError) + { + {($_ -eq $False)} + { + 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 + [System.IO.FileInfo]$FunctionPath = "$($InvokingScriptDirectory)\Functions\$($FunctionName).ps1" + [System.IO.DirectoryInfo]$FunctionDirectory = "$($FunctionPath.Directory)" + + $WriteLogMessage.Invoke(0, @("Function `'$($FunctionName)`' is beginning. Please Wait...")) + + #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)"} + $WriteLogMessage.Invoke(0, @("Available Function Parameter(s) = $($AvailableScriptParameters -Join ', ')")) + + [String[]]$SuppliedScriptParameters = $PSBoundParameters.GetEnumerator() | ForEach-Object {Try {"-$($_.Key):$($_.Value.GetType().Name)"} Catch {"-$($_.Key):Unknown"}} + $WriteLogMessage.Invoke(0, @("Supplied Function Parameter(s) = $($SuppliedScriptParameters -Join ', ')")) + + $WriteLogMessage.Invoke(0, @("Execution of $($FunctionName) began on $($FunctionStartTime.ToString($DateTimeLogFormat))")) + + #region Adjust security protocol type(s) + [System.Net.SecurityProtocolType]$DesiredSecurityProtocol = [System.Net.SecurityProtocolType]::TLS12 + + $WriteLogMessage.Invoke(0, @("Attempting to set the desired security protocol to `"$($DesiredSecurityProtocol.ToString().ToUpper())`". Please Wait...")) + + $Null = [System.Net.ServicePointManager]::SecurityProtocol = ($DesiredSecurityProtocol) + #endregion + + #Define default parameter values + Switch ($True) + { + {([String]::IsNullOrEmpty($BaseURI) -eq $True) -or ([String]::IsNullOrWhiteSpace($BaseURI) -eq $True)} + { + [System.URI]$BaseURI = 'https://api.github.com' + } + + {([String]::IsNullOrEmpty($ReleaseTag) -eq $True) -or ([String]::IsNullOrWhiteSpace($ReleaseTag) -eq $True)} + { + [System.String]$ReleaseTag = 'latest' + } + + {([String]::IsNullOrEmpty($DestinationDirectory) -eq $True) -or ([String]::IsNullOrWhiteSpace($DestinationDirectory) -eq $True)} + { + [System.IO.DirectoryInfo]$DestinationDirectory = "$($Env:Windir)\Temp\GitHubReleases\$($RepositoryName)" + } + + {([String]::IsNullOrEmpty($AssetInclusionExpression) -eq $True) -or ([String]::IsNullOrWhiteSpace($AssetInclusionExpression) -eq $True)} + { + [Regex]$AssetInclusionExpression = '(.*)' + } + + {([String]::IsNullOrEmpty($AssetExclusionExpression) -eq $True) -or ([String]::IsNullOrWhiteSpace($AssetExclusionExpression) -eq $True)} + { + [Regex]$AssetExclusionExpression = '^$' + } + } + + #Define helper function + #region Function Convert-FileSize + Function Convert-FileSize + { + [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 + + #Create an object that will contain the functions output. + $OutputObjectProperties = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary' + $OutputObjectProperties.RequestURI = $Null + $OutputObjectProperties.ReleaseInfo = $Null + $OutputObjectProperties.WebRequestResponseList = New-Object -TypeName 'System.Collections.Generic.List[System.Object]' + } + Catch + { + $ErrorHandlingDefinition.Invoke(2, $ContinueOnError.IsPresent) + } + Finally + { + + } + } + + Process + { + Try + { + #region Build the release request URI + $RequestURIBuilder = New-Object -TypeName 'System.Text.StringBuilder' + $Null = $RequestURIBuilder.Append($BaseURI.Scheme).Append('://') + $Null = $RequestURIBuilder.Append($BaseURI.DnsSafeHost) + $Null = $RequestURIBuilder.Append(':').Append($BaseURI.Port) + $Null = $RequestURIBuilder.Append('/').Append('repos') + $Null = $RequestURIBuilder.Append('/').Append($RepositoryOwner) + $Null = $RequestURIBuilder.Append('/').Append($RepositoryName) + $Null = $RequestURIBuilder.Append('/').Append('releases') + + Switch ($ReleaseTag.ToLower()) + { + {($_ -ieq 'latest')} + { + Switch ($IncludePreRelease.IsPresent) + { + {($_ -eq $True)} + { + # Get all releases and pick the first one (most recent) + $Null = $RequestURIBuilder.Append('?per_page=1') + } + + Default + { + # Use the /latest endpoint for non-prerelease + $Null = $RequestURIBuilder.Append('/').Append('latest') + } + } + } + Default + { + # Specific tag requested + $Null = $RequestURIBuilder.Append('/').Append('tags').Append('/').Append($ReleaseTag) + } + } + + [System.URI]$RequestURI = $RequestURIBuilder.ToString() + + $WriteLogMessage.Invoke(0, @("Request URI: $($RequestURI)")) + #endregion + + $OutputObjectProperties.RequestURI = $RequestURI + + #region Invoke the web request to get release information + $InvokeWebRequestHeaders = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary' + $InvokeWebRequestHeaders.'Accept' = 'application/vnd.github+json' + + Switch ($True) + { + {([String]::IsNullOrEmpty($AccessToken) -eq $False) -and ([String]::IsNullOrWhiteSpace($AccessToken) -eq $False)} + { + $InvokeWebRequestHeaders.Authorization = "Bearer $($AccessToken)" + } + } + + $InvokeWebRequestParameters = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary' + $InvokeWebRequestParameters.UseBasicParsing = $True + $InvokeWebRequestParameters.Uri = $RequestURI + $InvokeWebRequestParameters.UseDefaultCredentials = $True + $InvokeWebRequestParameters.DisableKeepAlive = $False + $InvokeWebRequestParameters.TimeoutSec = 30 + $InvokeWebRequestParameters.Method = 'Get' + $InvokeWebRequestParameters.ContentType = 'application/vnd.github+json' + $InvokeWebRequestParameters.UserAgent = [Microsoft.PowerShell.Commands.PSUserAgent]::Chrome + $InvokeWebRequestParameters.Verbose = $False + + Switch ($InvokeWebRequestHeaders.Keys.Count -gt 0) + { + {($_ -eq $True)} + { + $InvokeWebRequestParameters.Headers = $InvokeWebRequestHeaders + } + } + + $WriteLogMessage.Invoke(0, @("Attempting to retrieve release information. Please Wait...")) + + $InvokeWebRequestResult = Invoke-WebRequest @InvokeWebRequestParameters + + $WriteLogMessage.Invoke(0, @("Status Code: $($InvokeWebRequestResult.StatusCode)")) + + $OutputObjectProperties.WebRequestResponseList.Add($InvokeWebRequestResult) + #endregion + + #region Process the release response + Switch ($InvokeWebRequestResult.StatusCode -in @(200)) + { + {($_ -eq $True)} + { + $WebRequestResponseContent = $InvokeWebRequestResult.Content + + $ReleaseData = ConvertFrom-JSON -InputObject ($WebRequestResponseContent) + + # Handle array response (when using per_page for pre-releases) + Switch ($ReleaseData -is [System.Array]) + { + {($_ -eq $True)} + { + $ReleaseData = $ReleaseData[0] + } + } + + $ReleaseInfoProperties = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary' + $ReleaseInfoProperties.TagName = $ReleaseData.tag_name + $ReleaseInfoProperties.Name = $ReleaseData.name + $ReleaseInfoProperties.IsPreRelease = $ReleaseData.prerelease + $ReleaseInfoProperties.IsDraft = $ReleaseData.draft + $ReleaseInfoProperties.PublishedAt = $ReleaseData.published_at + $ReleaseInfoProperties.HTMLURL = $ReleaseData.html_url + $ReleaseInfoProperties.TarballURL = $ReleaseData.tarball_url + $ReleaseInfoProperties.ZipballURL = $ReleaseData.zipball_url + $ReleaseInfoProperties.Body = $ReleaseData.body + $ReleaseInfoProperties.AssetList = New-Object -TypeName 'System.Collections.Generic.List[System.Management.Automation.PSObject]' + + $OutputObjectProperties.ReleaseInfo = New-Object -TypeName 'System.Management.Automation.PSObject' -Property ($ReleaseInfoProperties) + + $WriteLogMessage.Invoke(0, @("Release Tag: $($OutputObjectProperties.ReleaseInfo.TagName)")) + + $WriteLogMessage.Invoke(0, @("Release Name: $($OutputObjectProperties.ReleaseInfo.Name)")) + + $WriteLogMessage.Invoke(0, @("Is Pre-Release: $($OutputObjectProperties.ReleaseInfo.IsPreRelease)")) + + $WriteLogMessage.Invoke(0, @("Published At: $($OutputObjectProperties.ReleaseInfo.PublishedAt)")) + + #region Process release assets + $ReleaseAssets = $ReleaseData.assets + + $ReleaseAssetCount = ($ReleaseAssets | Measure-Object).Count + + $WriteLogMessage.Invoke(0, @("Found $($ReleaseAssetCount) asset(s) in this release.")) + + ForEach ($Asset In $ReleaseAssets) + { + $AssetName = $Asset.name + + # Apply inclusion/exclusion filters + Switch ($AssetName -imatch $AssetInclusionExpression) + { + {($_ -eq $True)} + { + Switch ($AssetName -inotmatch $AssetExclusionExpression) + { + {($_ -eq $True)} + { + $AssetObjectProperties = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary' + $AssetObjectProperties.Name = $AssetName + $AssetObjectProperties.DownloadURL = $Asset.browser_download_url -As [System.URI] + $AssetObjectProperties.Size = $Asset.size -As [System.UInt64] + $AssetObjectProperties.ContentType = $Asset.content_type + $AssetObjectProperties.DownloadCount = $Asset.download_count + $AssetObjectProperties.CreatedAt = $Asset.created_at + $AssetObjectProperties.UpdatedAt = $Asset.updated_at + $AssetObjectProperties.DestinationPath = [System.IO.FileInfo]"$($DestinationDirectory.FullName)\$($AssetName)" + $AssetObjectProperties.DownloadStatus = 'NotAttempted' + $AssetObjectProperties.DownloadStartTime = $Null + $AssetObjectProperties.DownloadCompletionTime = $Null + $AssetObjectProperties.DownloadDuration = $Null + + $AssetObject = New-Object -TypeName 'System.Management.Automation.PSObject' -Property ($AssetObjectProperties) + + $OutputObjectProperties.ReleaseInfo.AssetList.Add($AssetObject) + + $WriteLogMessage.Invoke(0, @("Asset matched filters: $($AssetName) [Size: $((Convert-FileSize -Size $Asset.size).CalculatedSizeStr)]")) + } + } + } + } + } + #endregion + + $WriteLogMessage.Invoke(0, @("$($OutputObjectProperties.ReleaseInfo.AssetList.Count) asset(s) matched the inclusion/exclusion filters.")) + } + } + #endregion + + $OutputObject = New-Object -TypeName 'System.Management.Automation.PSObject' -Property ($OutputObjectProperties) + + # Convert AssetList to array if ReleaseInfo exists, otherwise ensure it's an empty array + Switch ($Null -ine $OutputObject.ReleaseInfo) + { + {($_ -eq $True)} + { + Try {$OutputObject.ReleaseInfo.AssetList = $OutputObject.ReleaseInfo.AssetList.ToArray()} Catch {$OutputObject.ReleaseInfo.AssetList = @()} + } + } + + #region Download assets if requested + Switch ($Download.IsPresent) + { + {($_ -eq $True)} + { + $AssetList = $OutputObject.ReleaseInfo.AssetList + + $AssetListCount = ($AssetList | Measure-Object).Count + + Switch ($AssetListCount -gt 0) + { + {($_ -eq $True)} + { + $AssetListCounter = 1 + + For ($AssetListIndex = 0; $AssetListIndex -lt $AssetListCount; $AssetListIndex++) + { + Try + { + $AssetItem = $AssetList[$AssetListIndex] + + $DownloadAssetItem = { + Param + ( + [System.Management.Automation.PSObject]$AssetItem + ) + + $WriteLogMessage.Invoke(0, @("Attempting to download asset $($AssetListCounter) of $($AssetListCount). Please Wait...")) + + $WriteLogMessage.Invoke(0, @("Asset Name: $($AssetItem.Name)")) + + $WriteLogMessage.Invoke(0, @("Download URL: $($AssetItem.DownloadURL)")) + + $WriteLogMessage.Invoke(0, @("Destination: $($AssetItem.DestinationPath)")) + + $WriteLogMessage.Invoke(0, @("Size: $((Convert-FileSize -Size $AssetItem.Size).CalculatedSizeStr)")) + + $WebClient = New-Object -TypeName 'System.Net.WebClient' + + Switch ([System.IO.Directory]::Exists($AssetItem.DestinationPath.Directory)) + { + {($_ -eq $False)} + { + $Null = [System.IO.Directory]::CreateDirectory($AssetItem.DestinationPath.Directory) + } + } + + $AssetItem.DownloadStatus = 'Attempted' + $AssetItem.DownloadStartTime = Get-Date + + $Null = $WebClient.DownloadFile($AssetItem.DownloadURL, $AssetItem.DestinationPath.FullName) + + $AssetItem.DownloadCompletionTime = Get-Date + $AssetItem.DownloadDuration = New-TimeSpan -Start ($AssetItem.DownloadStartTime) -End ($AssetItem.DownloadCompletionTime) + $AssetItem.DestinationPath = New-Object -TypeName 'System.IO.FileInfo' -ArgumentList @($AssetItem.DestinationPath.FullName) + $AssetItem.DownloadStatus = 'Success' + + $WriteLogMessage.Invoke(0, @("Download completed in $($AssetItem.DownloadDuration.TotalSeconds.ToString('F2')) second(s)")) + } + + Switch ([System.IO.File]::Exists($AssetItem.DestinationPath)) + { + {($_ -eq $True)} + { + Switch ($Force.IsPresent) + { + {($_ -eq $True)} + { + $Null = $DownloadAssetItem.InvokeReturnAsIs($AssetItem) + } + + Default + { + $WriteLogMessage.Invoke(0, @("Skipping download of asset $($AssetListCounter) of $($AssetListCount) - file already exists.")) + + $AssetItem.DownloadStatus = 'Skipped' + } + } + } + + Default + { + $Null = $DownloadAssetItem.InvokeReturnAsIs($AssetItem) + } + } + } + Catch + { + $AssetItem.DownloadStatus = 'Failed' + + $ErrorHandlingDefinition.Invoke(2, $ContinueOnError.IsPresent) + } + Finally + { + $AssetListCounter++ + } + } + } + } + } + } + #endregion + } + Catch + { + $ErrorHandlingDefinition.Invoke(2, $ContinueOnError.IsPresent) + } + Finally + { + + } + } + + End + { + Try + { + #Determine the date and time the function completed execution + $FunctionEndTime = (Get-Date) + + $WriteLogMessage.Invoke(0, @("Execution of $($FunctionName) ended on $($FunctionEndTime.ToString($DateTimeLogFormat))")) + + #Log the total script execution time + $FunctionExecutionTimespan = New-TimeSpan -Start ($FunctionStartTime) -End ($FunctionEndTime) + + $WriteLogMessage.Invoke(0, @("Function execution took $($FunctionExecutionTimespan.Hours.ToString()) hour(s), $($FunctionExecutionTimespan.Minutes.ToString()) minute(s), $($FunctionExecutionTimespan.Seconds.ToString()) second(s), and $($FunctionExecutionTimespan.Milliseconds.ToString()) millisecond(s)")) + + $WriteLogMessage.Invoke(0, @("Function `'$($FunctionName)`' is completed.")) + } + Catch + { + $ErrorHandlingDefinition.Invoke(2, $ContinueOnError.IsPresent) + } + Finally + { + Write-Output -InputObject ($OutputObject) + } + } + } +#endregion diff --git a/Toolkit/Functions/Get-InstalledSoftware.ps1 b/Toolkit/Functions/Get-InstalledSoftware.ps1 new file mode 100644 index 0000000..2b99e7d --- /dev/null +++ b/Toolkit/Functions/Get-InstalledSoftware.ps1 @@ -0,0 +1,670 @@ +## 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 PropertyList + Retrieves the value of each specified value name for a piece software. This will make the returned objects uniform. + + By default, the following properties are retrieved by default. Additional non specified properties are also added to the returned objects for easier locating of software outside of this function. + + .PARAMETER AllProperties + Retrieves the value of each value name that is detected for a piece of software. This will make the returned objects non-uniform because of how software vendors have not standardized this process. + + .PARAMETER AllUserProfiles + When this function is executed with the appropriate permissions, all user profile(s) can be searched for software by using their SIDs. The user profile(s) will be dynamically determined and associated with the pieces of software that are located. + + .PARAMETER AdditionalRegistryHiveObjects + Allows for searching additional registry hives as necessary in order to extend the search. See examples below. + + .PARAMETER ContinueOnError + Continues processing even if an error has occured. + + .EXAMPLE + Get-InstalledSoftware -AllProperties + + .EXAMPLE + $GetInstalledSoftwareParameters = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary' + $GetInstalledSoftwareParameters.FilterExpression = {($_.DisplayName -imatch '(^.*Google.*Chrome.*$)') -and ([Version]$_.DisplayVersion -ge [Version]'75.0.0.0')} + $GetInstalledSoftwareParameters.AllProperties = $False + $GetInstalledSoftwareParameters.AllUserProfiles = $True + $GetInstalledSoftwareParameters.ContinueOnError = $False + $GetInstalledSoftwareParameters.Verbose = $True + + $GetInstalledSoftwareResult = Get-InstalledSoftware @GetInstalledSoftwareParameters + + Write-Output -InputObject ($GetInstalledSoftwareResult) + + .EXAMPLE + $GetInstalledSoftwareParameters = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary' + $GetInstalledSoftwareParameters.FilterExpression = {($_.DisplayName -imatch '(^.*$)') -and ($_.DisplayName -inotmatch '(^.{0,0}$)')} + $GetInstalledSoftwareParameters.PropertyList = New-Object -TypeName 'System.Collections.Generic.List[String]' + $GetInstalledSoftwareParameters.PropertyList.Add('DisplayName') + $GetInstalledSoftwareParameters.PropertyList.Add('DisplayVersion') + $GetInstalledSoftwareParameters.PropertyList.Add('UninstallString') + $GetInstalledSoftwareParameters.PropertyList.Add('InstallLocation') + $GetInstalledSoftwareParameters.PropertyList.Add('Publisher') + $GetInstalledSoftwareParameters.PropertyList.Add('InstallDate') + $GetInstalledSoftwareParameters.ContinueOnError = $False + $GetInstalledSoftwareParameters.Verbose = $False + + $GetInstalledSoftwareResult = Get-InstalledSoftware @GetInstalledSoftwareParameters + + Write-Output -InputObject ($GetInstalledSoftwareResult) + + .EXAMPLE + $GetInstalledSoftwareParameters = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary' + $GetInstalledSoftwareParameters.FilterExpression = {($_.DisplayName -imatch '(^.*$)') -and ($_.DisplayName -inotmatch '(^.{0,0}$)')} + $GetInstalledSoftwareParameters.AllProperties = $True + $GetInstalledSoftwareParameters.AdditionalRegistryHiveObjects = New-Object -TypeName 'System.Collections.Generic.List[PSObject]' + + $RegistryHiveProperties = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary' + $RegistryHiveProperties.Add('Type', [Microsoft.Win32.RegistryHive]::Users) + $RegistryHiveProperties.Add('KeyList', (New-Object -TypeName 'System.Collections.Generic.List[String]')) + $RegistryHiveProperties.KeyList.Add("SID\Software\Microsoft\Windows\CurrentVersion\Uninstall") + + Switch ([System.Environment]::Is64BitOperatingSystem) + { + {($_ -eq $True)} + { + $RegistryHiveProperties.KeyList.Add("SID\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall") + } + } + + $RegistryHiveObject = New-Object -TypeName 'PSObject' -Property ($RegistryHiveProperties) + + $GetInstalledSoftwareParameters.AdditionalRegistryHiveObjects.Add($RegistryHiveObject) + + $GetInstalledSoftwareParameters.ContinueOnError = $False + $GetInstalledSoftwareParameters.Verbose = $True + + $GetInstalledSoftwareResult = Get-InstalledSoftware @GetInstalledSoftwareParameters + + Write-Output -InputObject ($GetInstalledSoftwareResult) + #> + + [CmdletBinding(DefaultParameterSetName = 'PropertyList')] + + Param + ( + [Parameter(Mandatory=$False)] + [ValidateNotNullOrEmpty()] + [Alias('IE')] + [ScriptBlock]$FilterExpression, + + [Parameter(Mandatory=$False, ParameterSetName = 'PropertyList')] + [Alias('PL')] + [String[]]$PropertyList, + + [Parameter(Mandatory=$False, ParameterSetName = 'AllProperties')] + [Alias('AP')] + [Switch]$AllProperties, + + [Parameter(Mandatory=$False)] + [Alias('AUP')] + [Switch]$AllUserProfiles, + + [Parameter(Mandatory=$False)] + [ValidateNotNullOrEmpty()] + [Alias('ARH')] + [PSObject[]]$AdditionalRegistryHiveObjects, + + [Parameter(Mandatory=$False)] + [Alias('COE')] + [Switch]$ContinueOnError + ) + + Begin + { + Try + { + #Determine the date and time we executed the function + $FunctionStartTime = (Get-Date) + + $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 + } + } + } + + [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) + { + {($Null -ieq $PropertyList) -or ($PropertyList.Count -eq 0)} + { + $OutputObjectValueList = New-Object -TypeName 'System.Collections.Generic.List[String]' + $OutputObjectValueList.Add('DisplayName') + $OutputObjectValueList.Add('DisplayVersion') + $OutputObjectValueList.Add('UninstallString') + $OutputObjectValueList.Add('QuietUninstallString') + $OutputObjectValueList.Add('InstallLocation') + $OutputObjectValueList.Add('Publisher') + $OutputObjectValueList.Add('InstallDate') + + [String[]]$PropertyList = $OutputObjectValueList.ToArray() + } + } + #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 + { + $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') + + Switch ([System.Environment]::Is64BitOperatingSystem) + { + {($_ -eq $True)} + { + $RegistryHiveProperties.KeyList.Add('Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall') + } + } + + $RegistryHiveObject = New-Object -TypeName 'PSObject' -Property ($RegistryHiveProperties) + + $RegistryHiveList.Add($RegistryHiveObject) + + Switch ($AllUserProfiles.IsPresent) + { + {($_ -eq $True)} + { + $UserProfilePropertyList = New-Object -TypeName 'System.Collections.Generic.List[Object]' + $UserProfilePropertyList.Add('LocalPath') + $UserProfilePropertyList.Add('SID') + $UserProfilePropertyList.Add(@{Name = 'NTAccount'; Expression = {Try {(New-Object -TypeName 'System.Security.Principal.SecurityIdentifier' -ArgumentList @($_.SID)).Translate([System.Security.Principal.NTAccount]).Value} Catch {$Null}}}) + $UserProfilePropertyList.Add('Special') + $UserProfilePropertyList.Add('Loaded') + + $UserProfileList = Get-CIMInstance -Namespace 'root\CIMv2' -Class 'Win32_UserProfile' -Verbose:$False | Where-Object {($_.Special -eq $False)} | Select-Object -Property ($UserProfilePropertyList) | Where-Object {([String]::IsNullOrEmpty($_.NTAccount) -eq $False) -and ([String]::IsNullOrWhiteSpace($_.NTAccount) -eq $False)} + + ForEach ($UserProfile In $UserProfileList) + { + $RegistryHiveProperties = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary' + $RegistryHiveProperties.Add('Type', [Microsoft.Win32.RegistryHive]::Users) + $RegistryHiveProperties.Add('NTAccount', $UserProfile.NTAccount) + $RegistryHiveProperties.Add('SID', $UserProfile.SID) + $RegistryHiveProperties.Add('KeyList', (New-Object -TypeName 'System.Collections.Generic.List[String]')) + $RegistryHiveProperties.KeyList.Add("$($RegistryHiveProperties.SID)\Software\Microsoft\Windows\CurrentVersion\Uninstall") + + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Attempting to add user profile `"$($RegistryHiveProperties.NTAccount)`" [SID: $($RegistryHiveProperties.SID)] to the search for installed software. Please Wait..." + Write-Verbose -Message ($LoggingDetails.LogMessage) + + Switch ([System.Environment]::Is64BitOperatingSystem) + { + {($_ -eq $True)} + { + $RegistryHiveProperties.KeyList.Add("$($RegistryHiveProperties.SID)\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall") + } + } + + $RegistryHiveObject = New-Object -TypeName 'PSObject' -Property ($RegistryHiveProperties) + + $RegistryHiveList.Add($RegistryHiveObject) + } + } + } + + ForEach ($AdditionalRegistryHiveObject In $AdditionalRegistryHiveObjects) {$RegistryHiveList.Add($AdditionalRegistryHiveObject)} + + For ($RegistryHiveListIndex = 0; $RegistryHiveListIndex -lt $RegistryHiveList.Count; $RegistryHiveListIndex++) + { + $RegistryHive = $RegistryHiveList[$RegistryHiveListIndex] + + $RegistryHivePropertyList = $RegistryHive.PSObject.Properties + + [Char[]]$HiveNameCharacterArray = ForEach ($Character in $RegistryHive.Type.ToString().ToCharArray()) + { + If ([Char]::IsUpper($Character)) + { + '_' + } + + $Character + } + + $HiveName = 'HKEY' + ($HiveNameCharacterArray -Join '').Trim().ToUpper() + + Switch ([System.Environment]::Is64BitOperatingSystem) + { + {($_ -eq $True)} + { + [Microsoft.Win32.RegistryView]$RegistryView = [Microsoft.Win32.RegistryView]::Registry64 + } + + Default + { + [Microsoft.Win32.RegistryView]$RegistryView = [Microsoft.Win32.RegistryView]::Registry32 + } + } + + $RegistryHiveObject = [Microsoft.Win32.RegistryKey]::OpenBaseKey($RegistryHive.Type, $RegistryView) + + For ($KeyListIndex = 0; $KeyListIndex -lt $RegistryHive.KeyList.Count; $KeyListIndex++) + { + $RegistryKey = $RegistryHive.KeyList[$KeyListIndex] + + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Attempting to search registry key path `"$($HiveName)\$($RegistryKey)`". Please Wait..." + Write-Verbose -Message ($LoggingDetails.LogMessage) + + $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.RegistryPath = $SubKeyObject.Name + $OutputObjectProperties.RegistryHive = $RegistryHive.Type.value__ + $OutputObjectProperties.RegistryHiveName = $HiveName + $OutputObjectProperties.RegistryLocation = ($SubKeyObjectSegments[1..$($SubKeyObjectSegments.GetUpperBound(0))]) -Join '\' + + ForEach ($OutputObjectValueName In $PropertyList) + { + $OutputObjectProperties.$($OutputObjectValueName) = $Null + } + + Switch ($AllProperties.IsPresent) + { + {($_ -eq $True)} + { + $ValueNameList = $SubKeyObject.GetValueNames() | Sort-Object + } + + Default + { + $ValueNameList = $PropertyList + } + } + + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Now processing entry `"$($OutputObjectProperties.RegistryPath)`" [ValueCount: $($ValueNameList.Count)]. Please Wait..." + Write-Verbose -Message ($LoggingDetails.LogMessage) + + Switch ($ValueNameList.Count -gt 0) + { + {($_ -eq $True)} + { + For ($ValueNameListIndex = 0; $ValueNameListIndex -lt $ValueNameList.Count; $ValueNameListIndex++) + { + Try + { + $ValueName = $ValueNameList[$ValueNameListIndex] + + $ValueKind = $SubKeyObject.GetValueKind($ValueName) + + Switch ($ValueKind) + { + {($_ -ieq 'PlaceHolder')} + { + + } + + Default + { + $Value = $SubKeyObject.GetValue($ValueName) + } + } + + $OutputObjectProperties.$($ValueName) = $Value + } + Catch + { + $OutputObjectProperties.$($ValueName) = $Null + } + Finally + { + + } + } + + $ProductCodePropertyList = New-Object -TypeName 'System.Collections.Generic.List[String]' + $ProductCodePropertyList.Add('UninstallString') + $ProductCodePropertyList.Add('QuietUninstallString') + $ProductCodePropertyList.Add('ModifyPath') + $ProductCodePropertyList.Add('RegistryLocation') + $ProductCodePropertyList.Add('RegistryPath') + + $OutputObjectProperties.ProductCode = $Null + + :ProductCodePropertyLoop ForEach ($ProductCodeProperty In $ProductCodePropertyList) + { + $ProductCodePropertyValue = $OutputObjectProperties.$($ProductCodeProperty) + + $ProductCodeMatch = [Regex]::Match($ProductCodePropertyValue, $RegularExpressionTable.GUID.ToString(), $RegexOptionList.ToArray()) + + Switch ($ProductCodeMatch.Success) + { + {($_ -eq $True)} + { + Switch (([String]::IsNullOrEmpty($OutputObjectProperties.ProductCode) -eq $True) -or ([String]::IsNullOrWhiteSpace($OutputObjectProperties.ProductCode) -eq $True)) + { + {($_ -eq $True)} + { + $OutputObjectProperties.ProductCode = Try {"`{$($ProductCodeMatch.Value.Trim().ToUpper() -ireplace '(\{)|(\})', '')`}"} Catch {$Null} + + Break ProductCodePropertyLoop + } + } + } + } + } + + Switch ($Null -ine $FilterExpression) + { + {($_ -eq $True)} + { + $FilterExpressionResult = [Boolean]($OutputObjectProperties | Where-Object -FilterScript $FilterExpression) + } + + Default + { + $FilterExpressionResult = $True + } + } + + Switch ($FilterExpressionResult) + { + {($_ -eq $True)} + { + ForEach ($OutputObjectProperty In ($OutputObjectProperties.Keys | Sort-Object)) + { + $OutputObjectPropertyName = $OutputObjectProperty + + $OutputObjectPropertyValue = $OutputObjectProperties.$($OutputObjectPropertyName) + + Switch ($OutputObjectPropertyName) + { + {($_ -iin @('DisplayVersion'))} + { + $ParsedVersion = New-Object -TypeName 'System.Version' + + $ParseVersionResult = [Version]::TryParse($OutputObjectPropertyValue, [Ref]$ParsedVersion) + + Switch ($ParseVersionResult) + { + {($_ -eq $True)} + { + $OutputObjectPropertyValue = $ParsedVersion + } + } + } + + {($_ -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 + } + } + } + + {($_ -iin @('UninstallString'))} + { + Switch (([String]::IsNullOrEmpty($OutputObjectPropertyValue) -eq $True) -or ([String]::IsNullOrWhiteSpace($OutputObjectPropertyValue) -eq $True)) + { + {($_ -eq $True)} + { + Switch (([String]::IsNullOrEmpty($OutputObjectProperties.ProductCode) -eq $False) -and ([String]::IsNullOrWhiteSpace($OutputObjectProperties.ProductCode) -eq $False)) + { + {($_ -eq $True)} + { + $OutputObjectPropertyValue = "msiexec.exe /x `"$($OutputObjectProperties.ProductCode)`"" + } + } + } + } + } + + {($_ -iin @('QuietUninstallString'))} + { + Switch (([String]::IsNullOrEmpty($OutputObjectPropertyValue) -eq $True) -or ([String]::IsNullOrWhiteSpace($OutputObjectPropertyValue) -eq $True)) + { + {($_ -eq $True)} + { + Switch (([String]::IsNullOrEmpty($OutputObjectProperties.ProductCode) -eq $False) -and ([String]::IsNullOrWhiteSpace($OutputObjectProperties.ProductCode) -eq $False)) + { + {($_ -eq $True)} + { + $OutputObjectPropertyValue = "msiexec.exe /x `"$($OutputObjectProperties.ProductCode)`" /qn /norestart REBOOT=ReallySuppress /L*v `"$($Env:Windir)\Temp\$(($OutputObjectProperties.DisplayName.Split([System.IO.Path]::GetInvalidFileNameChars()) -Join '') -ireplace '(\s+)', '_')_Removal.log`"" + } + } + } + } + } + + Default + { + Switch ($OutputObjectPropertyValue) + { + {($_ -imatch '(^\d+$)')} + { + $OutputObjectPropertyValue = $OutputObjectPropertyValue -As [System.Int64] + } + } + } + } + + $OutputObjectProperties.Remove($OutputObjectPropertyName) + + $OutputObjectProperties.$($OutputObjectPropertyName) = $OutputObjectPropertyValue + } + + Switch (($RegistryHivePropertyList.Name -icontains 'NTAccount') -and ($RegistryHivePropertyList.Name -icontains 'SID')) + { + {($_ -eq $True)} + { + $OutputObjectProperties.ProfileNTAccount = $RegistryHive.NTAccount -As [String] + $OutputObjectProperties.ProfileSID = $RegistryHive.SID -As [String] + } + + Default + { + $OutputObjectProperties.ProfileNTAccount = $Null -As [String] + $OutputObjectProperties.ProfileSID = $Null -As [String] + } + } + + $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 {} + } + + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Software Detection Filter: $($FilterExpression)" + Write-Verbose -Message ($LoggingDetails.LogMessage) -Verbose + + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Found $($OutputObjectList.Count) installed software entries matching the specified criteria." + Write-Verbose -Message ($LoggingDetails.LogMessage) -Verbose + } + Catch + { + $ErrorHandlingDefinition.Invoke() + } + Finally + { + + } + } + + 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 + { + $OutputObjectList = $OutputObjectList.ToArray() + + Write-Output -InputObject ($OutputObjectList) + } + } + } +#endregion \ No newline at end of file diff --git a/Toolkit/Functions/Get-MSIPropertyList.ps1 b/Toolkit/Functions/Get-MSIPropertyList.ps1 new file mode 100644 index 0000000..52b228f --- /dev/null +++ b/Toolkit/Functions/Get-MSIPropertyList.ps1 @@ -0,0 +1,105 @@ +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) + + $Null = [System.GC]::Collect() + + $Null = [System.GC]::WaitForPendingFinalizers() + } + } + 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 + { + Try + { + $Null = $View.Close() + $Null = [System.Runtime.Interopservices.Marshal]::ReleaseComObject($View) + $Null = [System.Runtime.Interopservices.Marshal]::ReleaseComObject($MSIDatabase) + $Null = [System.Runtime.Interopservices.Marshal]::ReleaseComObject($ComObject) + } + Catch + { + + } + } + } + + End + { + $OutputObjectList = $OutputObjectList.ToArray() + + Write-Output -InputObject ($OutputObjectList) + } + } \ No newline at end of file diff --git a/Toolkit/Functions/Get-NetworkInterfaceList.ps1 b/Toolkit/Functions/Get-NetworkInterfaceList.ps1 new file mode 100644 index 0000000..856792a --- /dev/null +++ b/Toolkit/Functions/Get-NetworkInterfaceList.ps1 @@ -0,0 +1,390 @@ +## Microsoft Function Naming Convention: http://msdn.microsoft.com/en-us/library/ms714428(v=vs.85).aspx + +#region Function Get-NetworkInterfaceList +Function Get-NetworkInterfaceList + { + <# + .SYNOPSIS + Returns a list of the network interface(s) available on the device this function is being executed on. + + .DESCRIPTION + Slightly more detailed description of what your function does + + .PARAMETER InterfaceTypeExpression + A valid regular expression that will allow for filtering the available network interface(s) based on their interface type. + + .PARAMETER OperationalStatusExpression + A valid regular expression that will allow for filtering the available network interface(s) based on their operational status. + + .EXAMPLE + Get-NetworkInterfaceList + + .EXAMPLE + $GetNetworkInterfaceListParameters = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary' + $GetNetworkInterfaceListParameters.InterfaceTypeExpression = "(.*Ethernet.*)|(.*Wireless.*)" + $GetNetworkInterfaceListParameters.OperationalStatusExpression = "(^Up$)" + $GetNetworkInterfaceListParameters.ContinueOnError = $False + $GetNetworkInterfaceListParameters.Verbose = $True + $GetNetworkInterfaceListParameters.ErrorAction = [System.Management.Automation.Actionpreference]::Stop + + $GetNetworkInterfaceListResult = Get-NetworkInterfaceList @GetNetworkInterfaceListParameters + + Write-Output -InputObject ($GetNetworkInterfaceListResult) + + .NOTES + Get Network Interface Types - '@(' + (([System.Enum]::GetNames('System.Net.NetworkInformation.NetworkInterfaceType') | Sort-Object | ForEach-Object {"'$($_)'"}) -Join ', ') + ')' + + Network Interface Type List - 'AsymmetricDsl', 'Atm', 'BasicIsdn', 'Ethernet', 'Ethernet3Megabit', 'FastEthernetFx', 'FastEthernetT', 'Fddi', 'GenericModem', 'GigabitEthernet', 'HighPerformanceSerialBus', 'IPOverAtm', 'Isdn', 'Loopback', 'MultiRateSymmetricDsl', 'Ppp', 'PrimaryIsdn', 'RateAdaptDsl', 'Slip', 'SymmetricDsl', 'TokenRing', 'Tunnel', 'Unknown', 'VeryHighSpeedDsl', 'Wireless80211', 'Wman', 'Wwanpp', 'Wwanpp2' + + Get Operational Status List - '@(' + (([System.Enum]::GetNames('System.Net.NetworkInformation.OperationalStatus') | Sort-Object | ForEach-Object {"'$($_)'"}) -Join ', ') + ')' + + Operational Status List - 'Dormant', 'Down', 'LowerLayerDown', 'NotPresent', 'Testing', 'Unknown', 'Up' + + .LINK + https://learn.microsoft.com/en-us/dotnet/api/system.net.networkinformation + #> + + [CmdletBinding(ConfirmImpact = 'Low', DefaultParameterSetName = '__AllParameterSets', HelpURI = 'https://learn.microsoft.com/en-us/dotnet/api/system.net.networkinformation', PositionalBinding = $True)] + + Param + ( + [Parameter(Mandatory=$False)] + [ValidateNotNullOrEmpty()] + [Regex]$InterfaceTypeExpression, + + [Parameter(Mandatory=$False)] + [ValidateNotNullOrEmpty()] + [Regex]$OperationalStatusExpression, + + [Parameter(Mandatory=$False)] + [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 + + #Create an object that will contain the functions output. + $OutputObjectList = New-Object -TypeName 'System.Collections.Generic.List[PSObject]' + + #Set default parameter value(s) + Switch ($True) + { + {([String]::IsNullOrEmpty($InterfaceTypeExpression) -eq $True) -or ([String]::IsNullOrWhiteSpace($InterfaceTypeExpression) -eq $True)} + { + [Regex]$InterfaceTypeExpression = '(.*)' + } + + {([String]::IsNullOrEmpty($OperationalStatusExpression) -eq $True) -or ([String]::IsNullOrWhiteSpace($OperationalStatusExpression) -eq $True)} + { + [Regex]$OperationalStatusExpression = '(.*)' + } + } + + #Define additional variables + $AddressFamilyTable = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary' + $AddressFamilyTable.IPv4 = New-Object -TypeName 'System.Collections.Generic.List[String]' + $AddressFamilyTable.IPv4.Add('InterNetwork') + $AddressFamilyTable.IPv6 = New-Object -TypeName 'System.Collections.Generic.List[String]' + $AddressFamilyTable.IPv6.Add('InterNetworkV6') + + $NetworkAdapterConfigurationClass = Get-CIMInstance -Namespace 'Root\CIMv2' -ClassName 'Win32_NetworkAdapterConfiguration' -Property @('*') + } + Catch + { + $ErrorHandlingDefinition.Invoke() + } + Finally + { + + } + } + + Process + { + Try + { + $LoggingDetails.LogMessage = "$($GetCurrentDateTimeMessageFormat.Invoke()) - Attempting to get the list of available network adapter(s). Please Wait..." + Write-Verbose -Message ($LoggingDetails.LogMessage) + + #region Network Adapter Information + Switch ([System.Net.NetworkInformation.NetworkInterface]::GetIsNetworkAvailable()) + { + {($_ -eq $True)} + { + $LogMessage = "Attempting to enumerate network interface(s). Please Wait..." + Write-Verbose -Message "$($LogMessage)" + + $NetworkInterfaces = [System.Net.NetworkInformation.NetworkInterface]::GetAllNetworkInterfaces() | Where-Object {($_.NetworkInterfaceType -imatch $InterfaceTypeExpression) -and ($_.OperationalStatus -imatch $OperationalStatusExpression)} + + $NetworkInterfacesCount = ($NetworkInterfaces | Measure-Object).Count + + $LogMessage = "Found $($NetworkInterfacesCount) available network interface(s) with an interface type matching `"$($InterfaceTypeExpression)`" and an operational status matching `"$($OperationalStatusExpression)`"." + Write-Verbose -Message "$($LogMessage)" + + :NetworkInterfaceLoop ForEach ($NetworkInterface In $NetworkInterfaces) + { + $NetworkInterfaceProperties = New-Object -TypeName 'System.Collections.Specialized.OrderedDictionary' + $NetworkInterfaceProperties.InterfaceName = $Null + $NetworkInterfaceProperties.Type = $Null + $NetworkInterfaceProperties.Status = $Null + $NetworkInterfaceProperties.Speed = 0 + $NetworkInterfaceProperties.MACAddress = $Null + $NetworkInterfaceProperties.IPv4DHCPServer = $Null + $NetworkInterfaceProperties.IPv4Address = $Null + $NetworkInterfaceProperties.IPv4Gateway = $Null + $NetworkInterfaceProperties.IPv4SubnetMask = $Null + $NetworkInterfaceProperties.IPv4PrefixLength = 0 + $NetworkInterfaceProperties.IPv4DNSServers = New-Object -TypeName 'System.Collections.Generic.List[String]' + $NetworkInterfaceProperties.IPv6Address = $Null + $NetworkInterfaceProperties.DNSEnabled = $Null + $NetworkInterfaceProperties.DynamicDNSEnabled = $Null + $NetworkInterfaceProperties.DNSSuffix = $Null + $NetworkInterfaceProperties.Description = $Null + $NetworkInterfaceProperties.DNSServerSearchOrder = New-Object -TypeName 'System.Collections.Generic.List[String]' + $NetworkInterfaceProperties.DNSDomainSuffixSearchOrder = New-Object -TypeName 'System.Collections.Generic.List[String]' + $NetworkInterfaceProperties.ID = $Null + + $NetworkInterfaceProperties.InterfaceName = $NetworkInterface.Name + $NetworkInterfaceProperties.Description = $NetworkInterface.Description + $NetworkInterfaceProperties.Type = $NetworkInterface.NetworkInterfaceType -As [String] + $NetworkInterfaceProperties.Status = $NetworkInterface.OperationalStatus -As [String] + $NetworkInterfaceProperties.Speed = Try {($NetworkInterface.Speed / 1000000) -As [Int]} Catch {0 -As [Int]} + $NetworkInterfaceProperties.ID = $NetworkInterface.ID + + $NetworkInterfaceMACAddress = If ($NetworkInterface.GetPhysicalAddress() -imatch '.*\:.*') {$NetworkInterface.GetPhysicalAddress()} Else {($NetworkInterface.GetPhysicalAddress() -ireplace '([a-zA-Z0-9]{2,2})', '$1:').TrimEnd(':').ToUpper()} + + $NetworkInterfaceProperties.MACAddress = $NetworkInterfaceMACAddress + + $NetworkInterfaceIPProperties = $NetworkInterface.GetIPProperties() + + $NetworkInterfaceProperties.DNSEnabled = $NetworkInterfaceIPProperties.IsDnsEnabled + $NetworkInterfaceProperties.DynamicDNSEnabled = $NetworkInterfaceIPProperties.IsDynamicDnsEnabled + $NetworkInterfaceProperties.DNSSuffix = $NetworkInterfaceIPProperties.DnsSuffix + + $NetworkInterfaceConfiguration = $NetworkAdapterConfigurationClass | Where-Object {($_.SettingID -ieq $NetworkInterface.ID)} + + Switch ($Null -ine $NetworkInterfaceConfiguration) + { + {($_ -eq $True)} + { + $NetworkInterfaceProperties.DNSServerSearchOrder = $NetworkInterfaceConfiguration.DNSServerSearchOrder + $NetworkInterfaceProperties.DNSDomainSuffixSearchOrder = $NetworkInterfaceConfiguration.DNSDomainSuffixSearchOrder + } + } + + ForEach ($UnicastAddress In $NetworkInterfaceIPProperties.UnicastAddresses) + { + Switch (([String]::IsNullOrEmpty($UnicastAddress.Address) -eq $False) -and ([String]::IsNullOrWhiteSpace($UnicastAddress.Address) -eq $False)) + { + {($_ -eq $True)} + { + Switch ($UnicastAddress.Address.AddressFamily) + { + {($_ -iin $AddressFamilyTable.IPv4)} + { + $NetworkInterfaceProperties.IPv4Address = $UnicastAddress.Address.IPAddressToString -As [String] + $NetworkInterfaceProperties.IPv4SubnetMask = $UnicastAddress.IPv4Mask -As [String] + $NetworkInterfaceProperties.IPv4PrefixLength = $UnicastAddress.PrefixLength + } + + {($_ -iin $AddressFamilyTable.IPv6)} + { + $NetworkInterfaceProperties.IPv6Address = $UnicastAddress.Address.IPAddressToString.ToUpper() + } + } + } + } + } + + ForEach ($GatewayAddress In $NetworkInterfaceIPProperties.GatewayAddresses) + { + Switch (([String]::IsNullOrEmpty($GatewayAddress.Address.Address) -eq $False) -and ([String]::IsNullOrWhiteSpace($GatewayAddress.Address.Address) -eq $False)) + { + {($_ -eq $True)} + { + Switch ($GatewayAddress.Address.AddressFamily) + { + {($_ -iin $AddressFamilyTable.IPv4)} + { + $NetworkInterfaceProperties.IPv4Gateway = $GatewayAddress.Address.IPAddressToString.ToString() + } + } + } + } + } + + ForEach ($DHCPServerAddress In $NetworkInterfaceIPProperties.DhcpServerAddresses) + { + Switch (([String]::IsNullOrEmpty($DHCPServerAddress.Address) -eq $False) -and ([String]::IsNullOrWhiteSpace($DHCPServerAddress.Address) -eq $False)) + { + {($_ -eq $True)} + { + Switch ($DHCPServerAddress.AddressFamily) + { + {($_ -iin $AddressFamilyTable.IPv4)} + { + $NetworkInterfaceProperties.IPv4DHCPServer = $DHCPServerAddress.IPAddressToString.ToString() + } + } + } + } + } + + ForEach ($DNSAddress In $NetworkInterfaceIPProperties.DnsAddresses) + { + Switch (([String]::IsNullOrEmpty($DNSAddress.Address) -eq $False) -and ([String]::IsNullOrWhiteSpace($DNSAddress.Address) -eq $False)) + { + {($_ -eq $True)} + { + Switch ($DNSAddress.AddressFamily) + { + {($_ -iin $AddressFamilyTable.IPv4)} + { + $NetworkInterfaceProperties.IPv4DNSServers.Add($DNSAddress.ToString()) + } + } + } + } + } + + $NetworkInterfaceObject = New-Object -TypeName 'PSObject' -Property ($NetworkInterfaceProperties) + + $OutputObjectList.Add($NetworkInterfaceObject) + } + + For ($NetworkInterfaceObjectListIndex = 0; $NetworkInterfaceObjectListIndex -lt $OutputObjectList.Count; $NetworkInterfaceObjectListIndex++) + { + $NetworkInterfaceItem = $OutputObjectList[$NetworkInterfaceObjectListIndex] + + $NetworkInterfaceNumber = ($NetworkInterfaceObjectListIndex + 1).ToString('00') + + $NetworkInterfacePropertyList = New-Object -TypeName 'System.Collections.Generic.List[String]' + + $Null = $NetworkInterfaceItem.PSObject.Properties | ForEach-Object {$NetworkInterfacePropertyList.Add("[$($_.Name): $($_.Value -Join ', ')]")} + + $LogMessage = "Network Interface #$($NetworkInterfaceNumber) - $($NetworkInterfacePropertyList -Join ' ')" + Write-Verbose -Message "$($LogMessage)" + } + } + + {($_ -eq $False)} + { + $WarningMessage = "There are no available network connections!" + Write-Warning -Message "$($WarningMessage)" + } + } + #endregion + } + Catch + { + $ErrorHandlingDefinition.Invoke() + } + Finally + { + + } + } + + 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 | Sort-Object -Property @('Status')) + } + } + } +#endregion \ No newline at end of file diff --git a/Toolkit/Toolkit.ps1 b/Toolkit/Toolkit.ps1 index 2283c62..731866f 100644 --- a/Toolkit/Toolkit.ps1 +++ b/Toolkit/Toolkit.ps1 @@ -1189,7 +1189,222 @@ Try } #endregion - #region This should always be the last command of the toolkit + #region Get Current User Session Information + $CurrentUserSessionDefinition = @" +using System; +using System.Runtime.InteropServices; +using System.Security.Principal; +using FILETIME = System.Runtime.InteropServices.ComTypes.FILETIME; + +public class CurrentUserSession +{ + [DllImport("wtsapi32.dll", SetLastError = true)] + static extern bool WTSQuerySessionInformation(IntPtr hServer, int sessionId, int wtsInfoClass, out IntPtr ppBuffer, out int pBytesReturned); + + [DllImport("wtsapi32.dll")] + static extern void WTSFreeMemory(IntPtr pMemory); + + [DllImport("kernel32.dll")] + static extern int GetCurrentProcessId(); + + [DllImport("kernel32.dll")] + static extern bool ProcessIdToSessionId(int processId, out int sessionId); + + [DllImport("winsta.dll", SetLastError = true)] + static extern int WinStationQueryInformation(IntPtr hServer, int sessionId, int information, ref WINSTATIONINFO buffer, int bufferLength, ref int returnedLength); + + [StructLayout(LayoutKind.Sequential)] + struct WINSTATIONINFO + { + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 70)] public byte[] Reserved1; + public int SessionId; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] public byte[] Reserved2; + public FILETIME ConnectTime; + public FILETIME DisconnectTime; + public FILETIME LastInputTime; + public FILETIME LoginTime; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1096)] public byte[] Reserved3; + public FILETIME CurrentTime; + } + + const int WTSSessionId = 4; + const int WTSUserName = 5; + const int WTSSessionName = 6; + const int WTSDomainName = 7; + const int WTSConnectState = 8; + const int WTSClientBuildNumber = 9; + const int WTSClientName = 10; + const int WTSClientProtocolType = 16; + + static readonly string[] ConnectStates = { "Active", "Connected", "ConnectQuery", "Shadow", "Disconnected", "Idle", "Listen", "Reset", "Down", "Init" }; + + public string UserName { get; private set; } + public string DomainName { get; private set; } + public string NTAccount { get; private set; } + public string SID { get; private set; } + public int SessionId { get; private set; } + public string SessionName { get; private set; } + public string ConnectState { get; private set; } + public bool IsConsoleSession { get; private set; } + public bool IsRdpSession { get; private set; } + public bool IsActiveSession { get; private set; } + public string ClientName { get; private set; } + public string ClientProtocol { get; private set; } + public DateTime? LogonTime { get; private set; } + public DateTime? DisconnectTime { get; private set; } + public TimeSpan? IdleTime { get; private set; } + public System.IO.DirectoryInfo ProfilePath { get; private set; } + public System.IO.DirectoryInfo Desktop { get; private set; } + public System.IO.DirectoryInfo Documents { get; private set; } + public System.IO.DirectoryInfo Downloads { get; private set; } + public System.IO.DirectoryInfo AppDataLocal { get; private set; } + public System.IO.DirectoryInfo AppDataRoaming { get; private set; } + public System.IO.DirectoryInfo StartMenu { get; private set; } + public System.IO.DirectoryInfo Startup { get; private set; } + public System.IO.DirectoryInfo Temp { get; private set; } + + 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); + } + + static string QueryString(int sessionId, int infoClass) + { + IntPtr buffer; int len; + if (WTSQuerySessionInformation(IntPtr.Zero, sessionId, infoClass, out buffer, out len)) + { + string result = Marshal.PtrToStringAnsi(buffer); + WTSFreeMemory(buffer); + return result ?? string.Empty; + } + return string.Empty; + } + + static int QueryInt(int sessionId, int infoClass) + { + IntPtr buffer; int len; + if (WTSQuerySessionInformation(IntPtr.Zero, sessionId, infoClass, out buffer, out len)) + { + int result = Marshal.ReadInt32(buffer); + WTSFreeMemory(buffer); + return result; + } + return -1; + } + + static short QueryShort(int sessionId, int infoClass) + { + IntPtr buffer; int len; + if (WTSQuerySessionInformation(IntPtr.Zero, sessionId, infoClass, out buffer, out len)) + { + short result = Marshal.ReadInt16(buffer); + WTSFreeMemory(buffer); + return result; + } + return -1; + } + + public CurrentUserSession() + { + int sessionId; + ProcessIdToSessionId(GetCurrentProcessId(), out sessionId); + SessionId = sessionId; + + UserName = QueryString(sessionId, WTSUserName); + DomainName = QueryString(sessionId, WTSDomainName); + SessionName = QueryString(sessionId, WTSSessionName); + ClientName = QueryString(sessionId, WTSClientName); + + int state = QueryInt(sessionId, WTSConnectState); + ConnectState = (state >= 0 && state < ConnectStates.Length) ? ConnectStates[state] : "Unknown"; + IsActiveSession = (state == 0); + + short protocol = QueryShort(sessionId, WTSClientProtocolType); + IsRdpSession = (protocol == 2); + ClientProtocol = IsRdpSession ? "RDP" : "Console"; + + IsConsoleSession = SessionName.Equals("Console", StringComparison.OrdinalIgnoreCase); + + if (!string.IsNullOrEmpty(DomainName) && !string.IsNullOrEmpty(UserName)) + { + NTAccount = DomainName + "\\" + UserName; + try { SID = new NTAccount(NTAccount).Translate(typeof(SecurityIdentifier)).Value; } + catch { SID = string.Empty; } + } + + // Get session timing info - use registry as fallback for LogonTime + try + { + int retLen = 0; + WINSTATIONINFO wsInfo = new WINSTATIONINFO(); + int result = WinStationQueryInformation(IntPtr.Zero, sessionId, 8, ref wsInfo, Marshal.SizeOf(typeof(WINSTATIONINFO)), ref retLen); + if (result != 0) + { + LogonTime = FileTimeToDateTime(wsInfo.LoginTime); + DisconnectTime = FileTimeToDateTime(wsInfo.DisconnectTime); + DateTime? lastInput = FileTimeToDateTime(wsInfo.LastInputTime); + DateTime? current = FileTimeToDateTime(wsInfo.CurrentTime); + if (lastInput.HasValue && current.HasValue) IdleTime = current.Value - lastInput.Value; + } + } + catch { } + + // Fallback: get logon time from user profile registry + if (!LogonTime.HasValue && !string.IsNullOrEmpty(SID)) + { + try + { + using (var key = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\" + SID)) + { + if (key != null) + { + var localLow = key.GetValue("LocalProfileLoadTimeLow"); + var localHigh = key.GetValue("LocalProfileLoadTimeHigh"); + if (localLow != null && localHigh != null) + { + long ft = (((long)(int)localHigh) << 32) | ((uint)(int)localLow); + LogonTime = DateTime.FromFileTime(ft); + } + } + } + } + catch { } + } + + string profilePath = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); + ProfilePath = new System.IO.DirectoryInfo(profilePath); + Desktop = new System.IO.DirectoryInfo(Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory)); + Documents = new System.IO.DirectoryInfo(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments)); + Downloads = new System.IO.DirectoryInfo(System.IO.Path.Combine(profilePath, "Downloads")); + AppDataLocal = new System.IO.DirectoryInfo(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData)); + AppDataRoaming = new System.IO.DirectoryInfo(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)); + StartMenu = new System.IO.DirectoryInfo(Environment.GetFolderPath(Environment.SpecialFolder.StartMenu)); + Startup = new System.IO.DirectoryInfo(Environment.GetFolderPath(Environment.SpecialFolder.Startup)); + Temp = new System.IO.DirectoryInfo(System.IO.Path.GetTempPath().TrimEnd('\\')); + } +} +"@ + + Try + { + If (-not ([System.Management.Automation.PSTypeName]'CurrentUserSession').Type) + { + Add-Type -TypeDefinition $CurrentUserSessionDefinition -Language CSharp -ErrorAction Stop + } + + $CurrentUserSession = New-Object -TypeName 'CurrentUserSession' + + $WriteLogMessage.Invoke(0, @("Current User Session: [NTAccount: $($CurrentUserSession.NTAccount)] [IsConsoleSession: $($CurrentUserSession.IsConsoleSession)] [IsRdpSession: $($CurrentUserSession.IsRdpSession)] [IsActiveSession: $($CurrentUserSession.IsActiveSession)] [Profile Path: $($CurrentUserSession.ProfilePath)]")) + } + Catch + { + $WriteLogMessage.Invoke(2, @("Unable to load CurrentUserSession type definition. $($_.Exception.Message)")) + } + #endregion + + #region This should always be the last command of the toolkit $WriteLogMessage.Invoke(0, @("All required functions, modules, libraries, and variables have been loaded from the toolkit successfully.")) #endregion }