MegaBites-AI/Windows-powershell
0372
1# Copyright (c) Microsoft Corporation.2# Licensed under the MIT License.3using namespace System.Text4 5Set-StrictMode -Version 3.06$ErrorActionPreference = 'Stop'7 8Import-Module HelpersCommon9Import-Module PSSysLog10 11<#12 Define enums that mirror the internal enums used13 in product code. These are used to configure14 syslog logging.15#>16enum LogLevel17{18 LogAlways = 0x019 Critical = 0x120 Error = 0x221 Warning = 0x322 Informational = 0x423 Verbose = 0x524 Debug = 0x1425}26 27enum LogChannel28{29 Operational = 0x1030 Analytic = 0x1131}32 33enum LogKeyword34{35 Runspace = 0x136 Pipeline = 0x237 Protocol = 0x438 Transport = 0x839 Host = 0x1040 Cmdlets = 0x2041 Serializer = 0x4042 Session = 0x8043 ManagedPlugin = 0x10044}45 46# mac log command can emit json, so just use that47# we need to deconstruct the eventmessage to get the event id48# we also need to filter out the non-default messages49function Get-MacOsSyslogItems {50 param ([int]$processId, [string]$logId)51 $logArgs = "show", "--process", "$processId", "--style", "json"52 log $logArgs |53 ConvertFrom-Json |54 Where-Object { $_.category -eq "$logId" -and $_.messageType -eq "Default" } |55 ForEach-Object {56 $s = $_.eventMessage.IndexOf('[') + 157 $e = $_.EventMessage.IndexOf(']')58 $l = $e - $s59 if ($l -gt 0) {60 $eventId = $_.eventMessage.SubString($s, $l)61 }62 else {63 $eventId = "unknown"64 }65 $_ | Add-Member -MemberType NoteProperty -Name EventId -Value $eventId -PassThru66 }67}68 69<#70.SYNOPSIS71 Creates a powershell.config.json file with syslog settings72 73.PARAMETER logId74 The identifier to use for logging75 76.PARAMETER logLevel77 The optional logging level, see the LogLevel enum78 79.PARAMETER logChannels80 The optional logging channels to enable; see the LogChannel enum81 82.PARAMETER logKeywords83 The optional keywords to enable ; see the LogKeyword enum84#>85function WriteLogSettings86{87 param88 (89 [Parameter(Mandatory)]90 [ValidateNotNullOrEmpty()]91 [string] $LogId,92 93 [System.Nullable[LogLevel]] $LogLevel = $null,94 95 [LogChannel[]] $LogChannels = $null,96 97 [LogKeyword[]] $LogKeywords = $null,98 99 [switch] $ScriptBlockLogging100 )101 102 $filename = [Guid]::NewGuid().ToString('N')103 $fullPath = Join-Path -Path $TestDrive -ChildPath "$filename.config.json"104 105 $values = @{}106 $values['LogIdentity'] = $LogId107 108 if ($LogChannels -ne $null)109 {110 $values['LogChannels'] = $LogChannels -join ', '111 }112 113 if ($LogKeywords -ne $null)114 {115 $values['LogKeywords'] = $LogKeywords -join ', '116 }117 118 if ($LogLevel)119 {120 $values['LogLevel'] = $LogLevel.ToString()121 }122 123 if($IsWindows)124 {125 $values["Microsoft.PowerShell:ExecutionPolicy"] = "RemoteSigned"126 }127 128 if($ScriptBlockLogging.IsPresent)129 {130 $powerShellPolicies = @{131 ScriptBlockLogging = @{132 EnableScriptBlockLogging = $ScriptBlockLogging.IsPresent133 EnableScriptBlockInvocationLogging = $true134 }135 }136 137 $values['PowerShellPolicies'] = $powerShellPolicies138 }139 140 ConvertTo-Json -InputObject $values | Set-Content -Path $fullPath -ErrorAction Stop141 return $fullPath142}143 144function Get-RegEx145{146 param($SimpleMatch)147 148 $regex = $SimpleMatch -replace '\\', '\\'149 $regex = $regex -replace '\(', '\('150 $regex = $regex -replace '\)', '\)'151 $regex = $regex -replace '\[', '\['152 $regex = $regex -replace '\]', '\]'153 $regex = $regex -replace '\-', '\-'154 $regex = $regex -replace '\$', '\$'155 $regex = $regex -replace '\^', '\^'156 return $regex157}158 159Describe 'Basic SysLog tests on Linux' -Tag @('CI','RequireSudoOnUnix') {160 BeforeAll {161 [bool] $IsSupportedEnvironment = $IsLinux162 [string] $SysLogFile = [string]::Empty163 164 if ($IsSupportedEnvironment)165 {166 # TODO: Update to use a PowerShell specific syslog file167 if (Test-Path -Path '/var/log/syslog')168 {169 $SysLogFile = '/var/log/syslog'170 }171 elseif (Test-Path -Path '/var/log/messages')172 {173 $SysLogFile = '/var/log/messages'174 }175 else176 {177 # TODO: Look into journalctl and other variations.178 Write-Warning -Message 'Unsupported Linux syslog configuration.'179 $IsSupportedEnvironment = $false180 }181 [string] $powershell = Join-Path -Path $PSHOME -ChildPath 'pwsh'182 $scriptBlockCreatedRegExTemplate = @"183Creating Scriptblock text \(1 of 1\):#012{0}(โ|#012)*ScriptBlock ID: [0-9a-z\-]*#012Path:.*184"@185 186 }187 }188 189 BeforeEach {190 # generate a unique log application id191 [string] $logId = [Guid]::NewGuid().ToString('N')192 }193 194 It 'Verifies basic logging with no customizations' -Skip:(!$IsSupportedEnvironment) {195 $configFile = WriteLogSettings -LogId $logId196 & $powershell -NoProfile -SettingsFile $configFile -Command '$env:PSModulePath | out-null'197 198 # Get log entries from the last 100 that match our id and are after the time we launched Powershell199 $items = Get-PSSysLog -Path $SyslogFile -Id $logId -Tail 100 -Verbose -TotalCount 3200 201 $items | Should -Not -Be $null202 $items.Length | Should -BeGreaterThan 1203 $items[0].EventId | Should -BeExactly 'Perftrack_ConsoleStartupStart:PowershellConsoleStartup.WinStart.Informational'204 $items[1].EventId | Should -BeExactly 'NamedPipeIPC_ServerListenerStarted:NamedPipe.Open.Informational'205 $items[2].EventId | Should -BeExactly 'Perftrack_ConsoleStartupStop:PowershellConsoleStartup.WinStop.Informational'206 # if there are more items than expected...207 if ($items.Length -gt 3)208 {209 # Force reporting of the first unexpected item to help diagnosis210 $items[3] | Should -Be $null211 }212 }213 214 # Skip test as it is failing in PowerShell CI on Linux platform.215 # Tracking Issue: https://github.com/PowerShell/PowerShell/issues/17092216 It 'Verifies scriptblock logging' -Skip <#-Skip:(!$IsSupportedEnvironment)#> {217 $configFile = WriteLogSettings -LogId $logId -ScriptBlockLogging -LogLevel Verbose218 $script = @'219$PID220& ([scriptblock]::create("Write-Verbose 'testheader123' ;Write-verbose 'after'"))221'@222 $testFileName = 'test01.ps1'223 $testScriptPath = Join-Path -Path $TestDrive -ChildPath $testFileName224 $script | Out-File -FilePath $testScriptPath -Force225 $null = & $powershell -NoProfile -SettingsFile $configFile -Command $testScriptPath226 227 # Get log entries from the last 100 that match our id and are after the time we launched Powershell228 $items = Get-PSSysLog -Path $SyslogFile -Id $logId -Tail 100 -Verbose -TotalCount 18229 230 $items | Should -Not -Be $null231 $items.Count | Should -BeGreaterThan 2232 $createdEvents = $items | Where-Object {$_.EventId -eq 'ScriptBlock_Compile_Detail:ExecuteCommand.Create.Verbose'}233 $createdEvents.Count | Should -BeGreaterOrEqual 3234 235 # Verify we log that we are executing a file236 $createdEvents[0].Message | Should -Match ($scriptBlockCreatedRegExTemplate -f ".*/$testFileName")237 238 # Verify we log that we are the script to create the scriptblock239 $createdEvents[1].Message | Should -Match ($scriptBlockCreatedRegExTemplate -f (Get-RegEx -SimpleMatch $Script.Replace([System.Environment]::NewLine,"โ")))240 241 # Verify we log that we are executing the created scriptblock242 $createdEvents[2].Message | Should -Match ($scriptBlockCreatedRegExTemplate -f "Write\-Verbose 'testheader123' ;Write\-verbose 'after'")243 }244 245 # Skip test as it is failing in PowerShell CI on Linux platform.246 # Tracking Issue: https://github.com/PowerShell/PowerShell/issues/17092247 It 'Verifies scriptblock logging with null character' -Skip <#-Skip:(!$IsSupportedEnvironment)#> {248 $configFile = WriteLogSettings -LogId $logId -ScriptBlockLogging -LogLevel Verbose249 $script = @'250$PID251& ([scriptblock]::create("Write-Verbose 'testheader123$([char]0x0000)' ;Write-verbose 'after'"))252'@253 $testFileName = 'test01.ps1'254 $testScriptPath = Join-Path -Path $TestDrive -ChildPath $testFileName255 $script | Out-File -FilePath $testScriptPath -Force256 $null = & $powershell -NoProfile -SettingsFile $configFile -Command $testScriptPath257 258 # Get log entries from the last 100 that match our id and are after the time we launched Powershell259 $items = Get-PSSysLog -Path $SyslogFile -Id $logId -Tail 100 -Verbose -TotalCount 18260 261 $items | Should -Not -Be $null262 $items.Count | Should -BeGreaterThan 2263 $createdEvents = $items | Where-Object {$_.EventId -eq 'ScriptBlock_Compile_Detail:ExecuteCommand.Create.Verbose'}264 $createdEvents.Count | Should -BeGreaterOrEqual 3265 266 # Verify we log that we are executing a file267 $createdEvents[0].Message | Should -Match ($scriptBlockCreatedRegExTemplate -f ".*/$testFileName")268 269 # Verify we log that we are the script to create the scriptblock270 $createdEvents[1].Message | Should -Match ($scriptBlockCreatedRegExTemplate -f (Get-RegEx -SimpleMatch $Script.Replace([System.Environment]::NewLine,"โ")))271 272 # Verify we log that we are executing the created scriptblock273 $createdEvents[2].Message | Should -Match ($scriptBlockCreatedRegExTemplate -f "Write\-Verbose 'testheader123โ' ;Write\-verbose 'after'")274 }275 276 It 'Verifies logging level filtering works' -Skip:(!$IsSupportedEnvironment) {277 $configFile = WriteLogSettings -LogId $logId -LogLevel Warning278 $result = & $powershell -NoProfile -SettingsFile $configFile -Command '$PID'279 $result | Should -Not -BeNullOrEmpty280 281 # by default, PowerShell only logs informational events on startup. With Level = Warning, nothing should282 # have been logged. We'll collect all the syslog entries and look for $PID (there should be none).283 $items = Get-PSSysLog -Path $SyslogFile284 @($items).Count | Should -BeGreaterThan 0285 $logs = $items | Where-Object { $_.ProcessId -eq $result }286 $logs | Should -BeNullOrEmpty287 }288}289 290Describe 'Basic os_log tests on MacOS' -Tag @('CI','RequireSudoOnUnix') {291 BeforeAll {292 [bool] $IsSupportedEnvironment = $IsMacOS293 [bool] $persistenceEnabled = $false294 295 $currentWarningPreference = $WarningPreference296 $WarningPreference = "SilentlyContinue"297 298 if ($IsSupportedEnvironment)299 {300 # Check the current state.301 $persistenceEnabled = (Get-OSLogPersistence).Enabled302 if (!$persistenceEnabled)303 {304 # enable powershell log persistence to support exporting log entries305 # for each test306 Set-OsLogPersistence -Enable307 }308 }309 [string] $powershell = Join-Path -Path $PSHOME -ChildPath 'pwsh'310 $scriptBlockCreatedRegExTemplate = @'311Creating Scriptblock text \(1 of 1\):312{0}313ScriptBlock ID: [0-9a-z\-]*314Path:.*315'@316 }317 318 BeforeEach {319 if ($IsSupportedEnvironment)320 {321 # generate a unique log application id322 [string] $logId = [Guid]::NewGuid().ToString('N')323 324 # Generate a working directory and content file for Export-OSLog325 [string] $workingDirectory = Join-Path -Path $TestDrive -ChildPath $logId326 $null = New-Item -Path $workingDirectory -ItemType Directory -ErrorAction Stop327 328 [string] $contentFile = Join-Path -Path $workingDirectory -ChildPath ('pwsh.log.txt')329 # get log items after current time.330 [DateTime] $after = [DateTime]::Now331 }332 }333 334 AfterAll {335 $WarningPreference = $currentWarningPreference336 if ($IsSupportedEnvironment -and !$persistenceEnabled)337 {338 # disable persistence if it wasn't enabled339 Set-OsLogPersistence -Disable340 }341 }342 343 It 'Verifies basic logging with no customizations' -Skip:(!$IsMacOS) {344 try {345 $timeString = [DateTime]::Now.ToString('yyyy-MM-dd HH:mm:ss')346 $configFile = WriteLogSettings -LogId $logId347 copy-item $configFile /tmp/pwshtest.config.json348 $testPid = & $powershell -NoProfile -SettingsFile $configFile -Command '$PID'349 $items = Get-MacOsSyslogItems -processId $testPid -logId $logId350 351 $items | Should -Not -Be $null352 $items.Count | Should -BeGreaterThan 2353 $items.EventId | Should -Contain 'Perftrack_ConsoleStartupStart:PowershellConsoleStartup.WinStart.Informational'354 $items.EventId | Should -Contain 'NamedPipeIPC_ServerListenerStarted:NamedPipe.Open.Informational'355 $items.EventId | Should -Contain 'Perftrack_ConsoleStartupStop:PowershellConsoleStartup.WinStop.Informational'356 }357 catch {358 if (Test-Path $contentFile) {359 Send-VstsLogFile -Path $contentFile360 }361 throw362 }363 }364 365 It 'Verifies scriptblock logging' -Skip:(!$IsMacOS) {366 try {367 $script = @'368$PID369& ([scriptblock]::create("Write-Verbose 'testheader123' ;Write-verbose 'after'"))370'@371 $configFile = WriteLogSettings -ScriptBlockLogging -LogId $logId -LogLevel Verbose372 $testFileName = 'test01.ps1'373 $testScriptPath = Join-Path -Path $TestDrive -ChildPath $testFileName374 $script | Out-File -FilePath $testScriptPath -Force375 $testPid = & $powershell -NoProfile -SettingsFile $configFile -Command $testScriptPath376 $items = Get-MacOsSyslogItems -processId $testPid -logId $logId377 378 $items | Should -Not -Be $null379 $items.Count | Should -BeGreaterThan 2380 $createdEvents = $items | Where-Object {$_.EventId -eq 'ScriptBlock_Compile_Detail:ExecuteCommand.Create.Verbose'}381 $createdEvents.Count | Should -BeGreaterOrEqual 3382 383 $createdEvents | ConvertTo-Json | set-content /tmp/createdEvents.json384 385 # Verify we log that we are executing a file386 $createdEvents[0].EventMessage | Should -Match $testFileName387 388 # Verify we log that we are the script to create the scriptblock389 $createdEvents[1].EventMessage | Should -Match (Get-RegEx -SimpleMatch $Script)390 391 # Verify we log that we are executing the created scriptblock392 $createdEvents[2].EventMessage | Should -Match "Write-Verbose 'testheader123' ;Write-verbose 'after'"393 }394 catch {395 if (Test-Path $contentFile) {396 Send-VstsLogFile -Path $contentFile397 }398 throw399 }400 }401 402 It 'Verifies scriptblock logging with null character' -Skip:(!$IsMacOS) {403 try {404 $script = @'405$PID406& ([scriptblock]::create("Write-Verbose 'testheader123$([char]0x0000)' ;Write-verbose 'after'"))407'@408 $configFile = WriteLogSettings -ScriptBlockLogging -LogId $logId -LogLevel Verbose409 $testFileName = 'test02.ps1'410 $testScriptPath = Join-Path -Path $TestDrive -ChildPath $testFileName411 $script | Out-File -FilePath $testScriptPath -Force412 $testPid = & $powershell -NoProfile -SettingsFile $configFile -Command $testScriptPath | Select-Object -First 1413 414 $items = Get-MacOsSyslogItems -processId $testPid -logId $logId415 $items | convertto-json | set-content /tmp/items.json416 417 $createdEvents = $items | Where-Object {$_.EventId -eq 'ScriptBlock_Compile_Detail:ExecuteCommand.Create.Verbose'}418 419 # Verify we log that we are executing a file420 $createdEvents[0].EventMessage | Should -Match $testFileName421 422 # Verify we log the null in the message423 $createdEvents[1].EventMessage | Should -Match "Write-Verbose 'testheader123\`$\(\[char\]0x0000\)' ;Write-verbose 'after'"424 }425 catch {426 if (Test-Path $contentFile) {427 Send-VstsLogFile -Path $contentFile428 }429 throw430 }431 }432 433 # this is now specific to MacOS434 It 'Verifies logging level filtering works' -skip:(!$IsMacOs) {435 $configFile = WriteLogSettings -LogId $logId -LogLevel Warning436 $testPid = & $powershell -NoLogo -NoProfile -SettingsFile $configFile -Command '$PID'437 438 $items = Get-MacOsSyslogItems -processId $testPid -logId $logId439 $items | Should -Be $null -Because ("{0} Warning event logs were found" -f @($items).Count)440 }441}442 443Describe 'Basic EventLog tests on Windows' -Tag @('CI','RequireAdminOnWindows') {444 BeforeAll {445 [bool] $IsSupportedEnvironment = $IsWindows446 [string] $powershell = Join-Path -Path $PSHOME -ChildPath 'pwsh'447 448 $currentWarningPreference = $WarningPreference449 $WarningPreference = "SilentlyContinue"450 451 $scriptBlockLoggingCases = @(452 @{453 name = 'normal script block'454 script = "Write-Verbose 'testheader123' ;Write-verbose 'after'"455 expectedText="Write-Verbose 'testheader123' ;Write-verbose 'after'`r`n"456 }457 @{458 name = 'script block with Null'459 script = "Write-Verbose 'testheader123$([char]0x0000)' ;Write-verbose 'after'"460 expectedText="Write-Verbose 'testheader123โ' ;Write-verbose 'after'`r`n"461 }462 )463 464 if ($IsSupportedEnvironment)465 {466 & "$PSHOME\RegisterManifest.ps1"467 }468 }469 470 AfterAll {471 $WarningPreference = $currentWarningPreference472 }473 474 BeforeEach {475 if ($IsSupportedEnvironment)476 {477 # generate a unique log application id478 [string] $logId = [Guid]::NewGuid().ToString('N')479 480 $logName = 'PowerShellCore'481 482 # get log items after current time.483 [DateTime] $after = [DateTime]::Now484 Clear-PSEventLog -Name "$logName/Operational"485 }486 }487 488 It 'Verifies scriptblock logging: <name>' -Skip:(!$IsSupportedEnvironment) -TestCases $scriptBlockLoggingCases {489 param(490 [string] $script,491 [string] $expectedText,492 [string] $name493 )494 $configFile = WriteLogSettings -ScriptBlockLogging -LogId $logId495 $testFileName = 'test01.ps1'496 $testScriptPath = Join-Path -Path $TestDrive -ChildPath $testFileName497 $script | Out-File -FilePath $testScriptPath -Force498 $null = & $powershell -NoProfile -SettingsFile $configFile -Command $testScriptPath499 500 $created = Wait-PSWinEvent -FilterHashtable @{ ProviderName=$logName; Id = 4104 } `501 -PropertyName Message -PropertyValue $expectedText502 503 $created | Should -Not -BeNullOrEmpty504 $created.Properties[0].Value | Should -Be 1505 $created.Properties[1].Value | Should -Be 1506 $created.Properties[2].Value | Should -Be $expectedText507 }508}509 