AACollectLogs.ps1
<#PSScriptInfo
.VERSION 1.0.2 .GUID 0fc474c8-1392-49fe-883d-a7837e8635f7 .AUTHOR bmcder@microsoft.com .COMPANYNAME .COPYRIGHT .TAGS AzureAutomationNotSupported .LICENSEURI .PROJECTURI .ICONURI .EXTERNALMODULEDEPENDENCIES .REQUIREDSCRIPTS .EXTERNALSCRIPTDEPENDENCIES .RELEASENOTES .DESCRIPTION This is a script to collect useful logs and diagnostic info for Microsoft Support for cases involving the following services/products Azure Arc enabled servers Azure Automation Hybrid Runbook Workers Automation Desired State Configuration(DSC) Automation Update Management Automation Change Tracking and Inventory #> $ErrorActionPreference = "SilentlyContinue" $global:useLogFile = $true $global:logFile = "" $global:IsAzure = $false [string]$global:defaultOutputDir = "" Function Write-Log { [cmdletbinding()] Param ( [Parameter(Mandatory=$False)] [ValidateSet("INFO","WARN","ERROR","FATAL","DEBUG")] [String] $Level = "INFO", [Parameter(Mandatory=$True)] [String] $FunctionName, [Parameter(Mandatory=$True)] [AllowEmptyString()] [String] $Message ) $timestamp = Get-Timestamp $log = "$timestamp || $Level || $FunctionName || $Message" if($global:uselogfile) { Add-Content $global:logfile -Value $log Write-Output $log } else { Write-Output $log } } function CheckRunningAsAdmin { Write-Log -FunctionName $MyInvocation.MyCommand -Message "Checking for elevated permissions..." if (-NOT ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) { Write-Log -FunctionName $MyInvocation.MyCommand -Message "Exception: Insufficient permissions to run this script. Open the PowerShell console as an administrator and run this script again." exit } } function Get-Timestamp { $timestamp = (Get-Date).ToString("hh_mm_MM_dd_yyyy") return $timestamp } function Get-TemporaryDirectoryLocation { $dirName = $env:computername $dirName += "_" $dirName += Get-Timestamp $path = "" try { $path = [System.IO.Path]::GetTempPath() + $dirName } catch { Write-Log -Level "Error" -FunctionName $MyInvocation.MyCommand -Message "Exception: $_.Exception.Message" } return $path } function CheckSoftwareInstalled { param( [String]$software ) $InstalledSoftware = (get-childitem HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall | Get-ItemProperty).DisplayName return ($InstalledSoftware -icontains $software) } function IsAzureVM { Write-Log -FunctionName $MyInvocation.MyCommand -Message "Checking if VM is on Azure..." $AzureMetadataDestination = "$global:defaultOutputDir\AzureMetadata.log" $response = Invoke-WebRequest -Uri "http://169.254.169.254/metadata/instance/compute?api-version=2019-06-01" -Headers @{Metadata = "true"} -TimeoutSec 1 -ErrorAction SilentlyContinue if ($null -ne $response -and $response.StatusCode -eq 200) { $global:IsAzure = $true $response.Content | out-file $AzureMetadataDestination Write-Log -FunctionName $MyInvocation.MyCommand -Message "VM is on Azure. Metadata collected" Write-Log -FunctionName $MyInvocation.MyCommand -Message "Collecting Guest Agent logs..." $CollectLogsExe = (get-childitem -Path c:\windowsazure -Filter CollectGuestLogs.exe -Recurse | sort-object LastAccessTime -desc | select-object -first 1).FullName & "$CollectLogsExe" -FileName:$global:defaultOutputDir\AzureGuestAgentLogs.zip } else { Write-Log -FunctionName $MyInvocation.MyCommand -Message "VM is on NOT Azure." $global:IsAzure = $false } } function IsMMAInstalled { CheckSoftwareInstalled "Microsoft Monitoring Agent" } function IsArcVM { CheckSoftwareInstalled "Azure Connected Machine Agent" } function Get-RegistryKeys { #"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\SystemCertificates\Microsoft Monitoring Agent\Certificates" # HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL # HKEY_USERS\S-1-5-18\Software\Microsoft\Windows\CurrentVersion\Internet Settings # HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Services\HealthService\Parameters\Http Connection Proxy Url "Hello" } function init { $global:defaultOutputDir = (Get-TemporaryDirectoryLocation).ToString() if($true -eq [string]::IsNullOrEmpty($global:defaultOutputDir)) { Write-Log -Level "Error" -FunctionName $MyInvocation.MyCommand -Message "Default output dir empty: $global:defaultOutputDir" throw 'init failed' } New-Item -Path $global:defaultOutputDir -ItemType "directory" -Force | Out-Null $global:logFile = $global:defaultOutputDir + "\tool.log" if (Test-Path $global:logFile) { Write-Output "Removing tool's old log file." Remove-Item $global:logFile -Force } if($True -eq $global:useLogFile) { Write-Output "Tool's log data is being redirected to $global:logFile" } Write-Log -FunctionName $MyInvocation.MyCommand -Message "Starting AA Log collection on $env:computername at $(Get-Date) local time = $((Get-Date).ToUniversalTime()) UTC" } function Get-SystemData { Write-Log -FunctionName $MyInvocation.MyCommand -Message "Collecting system information..." $SysInfoDestination = "$global:defaultOutputDir\sysinfo.log" try { systeminfo | Out-File $SysInfoDestination Write-Log -FunctionName $MyInvocation.MyCommand -Message "System information collected." } catch { Write-Log -FunctionName $MyInvocation.MyCommand -Message "Exception: $_.Exception.Message" Write-Log -FunctionName $MyInvocation.MyCommand -Message "Failed to collect system information." } } function Get-EventViewerLogs { [cmdletbinding()] Param ( [Parameter(Mandatory=$True)] [String] $Source, [Parameter(Mandatory=$True)] [String] $OutputFile ) Write-Log -FunctionName $MyInvocation.MyCommand -Message "Collecting event logs from: $Source" try { $log = Get-CIMInstance Win32_NTEventlogFile -Filter "LogFileName = ""$Source""" $log.BackupEventLog($OutputFile) | Out-Null if($True -eq $?) { Write-Log -FunctionName $MyInvocation.MyCommand -Message "Logs from source: $Source, stored at: $OutputFile" } } catch { Write-Log -Level "Error" -FunctionName $MyInvocation.MyCommand -Message "Exception: $_.Exception.Message" Write-Log -Level "Error" -FunctionName $MyInvocation.MyCommand -Message "Failed to collect event logs from: $Source" } } function CollectEventLogs { if ($global:IsAzure -eq $true) { Write-Log -FunctionName $MyInvocation.MyCommand -Message "We have already ran the Azure Guest Agent collection utility, so all event logs can be found in that zip. We will not duplicate the log collection" return } $eventsDirectory = "$global:defaultOutputDir\Event Logs" if ($False -eq (Test-Path $eventsDirectory) ) { New-Item -Path $eventsDirectory -ItemType "directory" -Force | Out-Null } $eventsSource = "Application" Get-EventViewerLogs -Source $eventsSource -OutputFile $eventsDirectory\$eventsSource.evtx $eventsSource = "System" Get-EventViewerLogs -Source $eventsSource -OutputFile $eventsDirectory\$eventsSource.evtx if (IsMMAInstalled) { $eventsSource = "Operations Manager" Get-EventViewerLogs -Source $eventsSource -OutputFile $eventsDirectory\$eventsSource.evtx } } function archiveLogs { Write-Log -FunctionName $MyInvocation.MyCommand -Message "Data collection completed." Copy-Item "$global:defaultOutputDir\..\tool.log" -Destination $global:defaultOutputDir Compress-Archive -Path "$global:defaultOutputDir" -DestinationPath "$global:defaultOutputDir.zip" Remove-Item -Recurse -Force -Path $global:defaultOutputDir Write-Log -FunctionName $MyInvocation.MyCommand -Message "Collected logs available at: $global:defaultOutputDir.zip" & "explorer" "/select,$global:defaultOutputDir.zip" } function get-ArcInfo { if (IsArcVM) { Write-Log -FunctionName $MyInvocation.MyCommand -Message "Collecting Arc logs..." azcmagent logs -o "$global:defaultOutputDir\azcmagentlogs.zip" } } function IsUsingChangeTracking { if ((get-childitem -Path "$($MMARegKey.InstallDirectory)Health Service State\" -Filter CT_*) -or (get-childitem -Path "$($MMARegKey.InstallDirectory)Health Service State\" -Filter FCT_*)) { return $true } } function get-MMAInfo { if (IsMMAInstalled) { Write-Log -FunctionName $MyInvocation.MyCommand -Message "Collecting MMA agent information..." $MMAgentDestination = "$global:defaultOutputDir\MMAgent.log" try { $MMARegKey = get-itemproperty -path "HKLM:\SOFTWARE\Microsoft\Microsoft Operations Manager\3.0\Setup" | Select-Object AgentVersion,InstallDirectory,InstalledOn $MMAgent = New-Object -ComObject 'AgentConfigManager.MgmtSvcCfg' $MMARegKey | format-list * | Out-File $MMAgentDestination $MMAgent.GetCloudWorkspaces() | Out-File $MMAgentDestination -append "MMA Proxy : $($MMAgent.ProxyURl)" >> $MMAgentDestination "MMA Proxy Username : $($MMAgent.ProxyUserName)" >> $MMAgentDestination Write-Log -FunctionName $MyInvocation.MyCommand -Message "MMA agent information collected." test-MMAConnectivity } catch { Write-Log -FunctionName $MyInvocation.MyCommand -Message "Exception: $_.Exception.Message" Write-Log -FunctionName $MyInvocation.MyCommand -Message "Failed to collect MMA agent information." } if (IsUsingChangeTracking) { Get-ChangeTrackingLogs } } else { Write-Log -FunctionName $MyInvocation.MyCommand -Message "MMA agent not installed." } } function test-MMAConnectivity { Write-Log -FunctionName $MyInvocation.MyCommand -Message "Checking MMA connectivity with testcloudconnection..." try { $TCCDestination = "$global:defaultOutputDir\TestCloudConnection.log" & "$($MMARegKey.InstallDirectory)TestCloudConnection.exe" | Out-File $TCCDestination Write-Log -FunctionName $MyInvocation.MyCommand -Message "MMA connectivity checked." } catch { Write-Log -FunctionName $MyInvocation.MyCommand -Message "Exception: $_.Exception.Message" Write-Log -FunctionName $MyInvocation.MyCommand -Message "Failed to collect testcloudconnection output." } } function Copy-WindowsUpdateETLs { Write-Log -FunctionName $MyInvocation.MyCommand -Message "Collecting Windows Update ETLs in case they haven't been formatted correctly..." new-item "$global:defaultOutputDir\WindowsUpdateETLs" -ItemType Directory copy-item C:\Windows\Logs\WindowsUpdate\*.etl -Destination "$global:defaultOutputDir\WindowsUpdateETLs\" } function Start-AUMTroubleshooter { Write-Log -FunctionName $MyInvocation.MyCommand -Message "Downloading AUM Troubleshooter..." Invoke-WebRequest -Uri "https://raw.githubusercontent.com/Azure/updatemanagement/main/UM_Windows_Troubleshooter_Offline.ps1" ` -OutFile "$global:defaultOutputDir\UM_Windows_Troubleshooter_Offline.ps1" Write-Log -FunctionName $MyInvocation.MyCommand -Message "Running AUM Troubleshooter..." & "$global:defaultOutputDir\UM_Windows_Troubleshooter_Offline.ps1" | out-file "$global:defaultOutputDir\AUMTroubleshooter.log" } function get-WindowsUpdateInfo { Write-Log -FunctionName $MyInvocation.MyCommand -Message "Getting Windows Update information..." $windowsUpdateFile = $Env:WinDir + "\Windowsupdate.log" if (Get-Command "Get-WindowsUpdateLog" -errorAction SilentlyContinue) { Convert-WindowsUpdateLog Copy-WindowsUpdateETLs } else { Copy-Item $windowsUpdateFile -Destination "$global:defaultOutputDir\WindowsUpdate.log" -Force } Start-AUMTroubleshooter # TODO: Get TRSOP result get-childitem HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\ >> "$global:defaultOutputDir\WindowsUpdateRegKey.txt" } function convert-WindowsUpdateLog { Write-Log -FunctionName $MyInvocation.MyCommand -Message "Converting raw Windows Update etls into readable WindowsUpdate.log file. This may take some time." Get-WindowsUpdateLog -LogPath "$global:defaultOutputDir\WindowsUpdate.log" Write-Log -FunctionName $MyInvocation.MyCommand -Message "Finished converting WindowsUpdate.log" } function get-HRWInfo { $IsHRW = $false Write-Log -FunctionName $MyInvocation.MyCommand -Message "Checking for installed HRWs..." if (test-path HKLM:\SOFTWARE\Microsoft\HybridRunbookWorker) { $IsHRW = $true $HRWv1Destination = "$global:defaultOutputDir\HRWv1.log" Write-Log -FunctionName $MyInvocation.MyCommand -Message "Agent HRWv1 installed." $MMARegKey = get-itemproperty -path "HKLM:\SOFTWARE\Microsoft\Microsoft Operations Manager\3.0\Setup" | Select-Object AgentVersion,InstallDirectory,InstalledOn $ReadHRWDetailsPath = (get-childitem -Path "$($MMARegKey.InstallDirectory)AzureAutomation\" -Filter Read-HybridRunbookWorkerDetails.ps1 -Recurse).FullName & "$ReadHRWDetailsPath" -LogFileFullPath:"$HRWv1Destination" } if (test-path HKLM:\SOFTWARE\Microsoft\HybridRunbookWorkerV2) { $IsHRW = $true Write-Log -FunctionName $MyInvocation.MyCommand -Message "HRWv2 extension installed." $HRWv2Destination = "$global:defaultOutputDir\HRWv2.log" get-childitem HKLM:\SOFTWARE\Microsoft\HybridRunbookWorkerv2 -Recurse | out-file $HRWv2Destination $HRWv2TShooterDestination = "$global:defaultOutputDir\HRWv2TShooter.log" $HRWv2exepath = get-itempropertyValue -path HKLM:\SYSTEM\CurrentControlSet\Services\HybridWorkerService -Name ImagePath $HRWv2FolderPath = $HRWv2exepath.Substring(0, ($HRWv2exepath.Length - 61)) Write-Log -FunctionName $MyInvocation.MyCommand -Message "Running HRWv2 extension TroubleshootWindowsExtension script." $HRWv2TroubleshooterScriptPath = $HRWv2FolderPath + "bin\troubleshooter\TroubleshootWindowsExtension.ps1" & $HRWv2TroubleshooterScriptPath | out-file $HRWv2TShooterDestination $PullLogsScript = (get-childitem -Path "C:\Packages\Plugins\Microsoft.Azure.Automation.HybridWorker.HybridWorkerForWindows\" -Filter PullLogs.ps1 -Recurse).FullName & "$PullLogsScript" "$global:defaultOutputDir\HRW2Logs.zip" } if ($IsHRW -eq $false) { Write-Log -Level WARN -FunctionName $MyInvocation.MyCommand -Message "No HRW installed." } } function get-ChangeTrackingLogs { Write-Log -FunctionName $MyInvocation.MyCommand -Message "Copying Change Tracking logs..." New-Item "global:defaultOutputDir\ChangeTrackingLogs" -ItemType Directory Get-ChildItem -Path "$($MMARegKey.InstallDirectory)Health Service State\CT_*" | Copy-Item -Destination "$global:defaultOutputDir\ChangeTrackingLogs" -recurse -Force -Verbose Get-ChildItem -Path "$($MMARegKey.InstallDirectory)Health Service State\FCT_*" | Copy-Item -Destination "$global:defaultOutputDir\ChangeTrackingLogs" -recurse -Force -Verbose } function get-DSClogs { if (Get-DscConfigurationStatus) { Write-Log -FunctionName $MyInvocation.MyCommand -Message "Getting DSC Diagnostic logs..." if (-not $(get-command New-XDscDiagnostics)) { Find-Module xDSCDiagnostics | Install-Module New-xDscDiagnosticsZip -destinationPath $global:defaultOutputDir\ } } else { Write-Log -FunctionName $MyInvocation.MyCommand -Message "Skipping DSC Diagnostic logs collection as DSC not in use..." } } ## MAIN BODY OF CODE START HERE init CheckRunningAsAdmin Get-SystemData IsAzureVm get-MMAInfo get-HRWInfo get-WindowsUpdateInfo get-ArcInfo CollectEventLogs get-DSCLogs archiveLogs |