Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,8 @@
"files.encoding": "utf8",
"[powershell]": {
"files.encoding": "utf8bom"
}
},
"cSpell.words": [
"cmdlet"
]
}
200 changes: 200 additions & 0 deletions Admin/Get-SimpleAuditLogReport.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
<#

.SYNOPSIS
Parses the output of Search-AdminAuditlog to produce more readable results.

.DESCRIPTION
Takes the output of the Search-AdminAuditlog as an input and reconstructs the
results into a more easily read structure.

Results can be stored in a variable and sent to the script with -searchresults
or taken directly off of a pipeline and converted.

Output should generally contain commands that can be copied and pasted into
an Exchange/Exchange Online Shell and run directly with little to no
Modification.

.PARAMETER SeachResults
Output of the Search-AdminAuditLog. Either stored in a variable or pipelined
into the script.

.PARAMETER ResolveCaller
Attempts to resolve the alias of the person who ran the command into the
primary SMTP address.

.PARAMETER Agree
Verifies you have read and agree to the disclaimer at the top of the script file

.OUTPUTS
Creates an output that contains the following information:

Caller : Person who ran the command
Cmdlet : Cmdlet that was run
FullCommand : Reconstructed full command that was run
RunDate : Date and Time command was run
ObjectModified : Object that was modified by the command

.EXAMPLE
$Search = Search-AdminAuditLog
$search | C:\Scripts\Get-SimpleAuditLogReport.ps1 -agree

Converts the results of Search-AdminAuditLog and sends the output to the screen

.EXAMPLE
Search-AdminAuditLog | C:\Scripts\Get-SimpleAuditlogReport.ps1 -agree | Export-CSV -path C:\temp\auditlog.csv

Converts the restuls of Search-AdminAuditLog and sends the output to a CSV file

.EXAMPLE
$MySearch = Search-AdminAuditLog -cmdlet set-mailbox
C:\Script\C:\Scripts\Get-SimpleAuditLogReport.ps1 -agree -searchresults $MySearch

Finds all instances of set-mailbox
Converts them by passing in the results to the switch searchresults
Outputs to the screen

#>

Param (
[Parameter(Position = 0, Mandatory = $true, ValueFromPipelinepeline = $true, ValueFromPipelineByPropertyName = $true)]
$SearchResults,
[switch]$ResolveCaller,
[switch]$Agree
)

# Setup to process incomming results
Begin {

# Statement to ensure that you have looked at the disclaimer or that you have removed this line so you don't have too
If ($Agree -ne $true) { Write-Error "Please run the script with -Agree to indicate that you have read and agreed to the sample script disclaimer at the top of the script file" -ErrorAction Stop }

# Make sure the array is null
[array]$ResultSet = $null

# Set the counter to 1
$i = 1

# If resolveCaller is called it can take much longer to run so notify the user of that
if ($ResolveCaller) { Write-Warning "ResolveCaller specified; Script will take significantly longer to run as it attemps to resolve the primary SMTP address of each calling user. Progress updates will be provided every 25 entries." }
}

# Process thru what ever is comming into the script
Process {

# Deal with each object in the input
$searchresults | ForEach-Object {

# Reset the result object
$Result = New-Object PSObject

# Get the alias of the User that ran the command
$user = ($_.caller.split("/"))[-1]

# If we used resolve caller then try to resolve the primary SMTP address of the calling user
if ($ResolveCaller) {

# attempt to resolve the recipient
[string]$Recipient = (get-recipient $user -ErrorAction silentlycontinue).primarysmtpaddress

# if we get a result then put that in the output otherwise do nothing
If (!([string]::IsNullOrEmpty($Recipient))) { $user = $Recipient }

# Since this is going to take longer to run provide status every 25 entries
if ($i % 25 -eq 0) { Write-Host "Processed 25 Results" }
$i++
}

# Build the command that was run
$switches = $_.cmdletparameters
[string]$FullCommand = $_.cmdletname

# Get all of the switchs and add them in "human" form to the output
foreach ($parameter in $switches) {

# Format our values depending on what they are so that they are as close
# a match as possible for what would have been entered
switch -regex ($parameter.value) {

# If we have a multi value array put in then we need to break it out and add quotes as needed
'[;]' {

# Reset the formatted value string
$FormattedValue = $null

# Split it into an array
$valuearray = $switch.current.split(";")

# For each entry in the array add quotes if needed and add it to the formatted value string
$valuearray | ForEach-Object {
if ($_ -match "[ \t]") { $FormattedValue = $FormattedValue + "`"" + $_ + "`";" }
else { $FormattedValue = $FormattedValue + $_ + ";" }
}

# Clean up the trailing ;
$FormattedValue = $FormattedValue.trimend(";")

# Add our switch + cleaned up value to the command string
$FullCommand = $FullCommand + " -" + $parameter.name + " " + $FormattedValue
}

# If we have a value with spaces add quotes
'[ \t]' { $FullCommand = $FullCommand + " -" + $parameter.name + " `"" + $switch.current + "`"" }

# If we have a true or false format them with :$ in front ( -allow:$true )
'^True$|^False$' { $FullCommand = $FullCommand + " -" + $parameter.name + ":`$" + $switch.current }

# Otherwise just put the switch and the value
default { $FullCommand = $FullCommand + " -" + $parameter.name + " " + $switch.current }
}
}
}

# Pull out the Modified properties
$ModifiedProperties = $_.modifiedproperties

# Make sure our holding variable are nulled out
$Property = $null
$Oldvalue = $null
$NewValue = $null

# Push each property set into a seperate string
$ModifiedProperties | ForEach-Object {
[string]$Property = $Property + $_.name + ";"
[string]$OldValue = $OldValue + $_.oldvalue + ";"
[string]$NewValue = $NewValue + $_.newvalue + ";"
}

# Trim off the last ;
$Property = $Property.TrimEnd(";")
$Oldvalue = $Oldvalue.TrimEnd(";")
$NewValue = $NewValue.TrimEnd(";")

# Format our modified object
if ([string]::IsNullOrEmpty($_.objectModified)) {
$ObjModified = ""
} else {
$ObjModified = ($_.objectmodified.split("/"))[-1]
$ObjModified = ($ObjModified.split("\"))[-1]
}

# Get just the name of the cmdlet that was run
[string]$cmdlet = $_.CmdletName

# Build the result object to return our values
$Result | Add-Member -MemberType NoteProperty -Value $user -Name Caller
$Result | Add-Member -MemberType NoteProperty -Value $cmdlet -Name Cmdlet
$Result | Add-Member -MemberType NoteProperty -Value $FullCommand -Name FullCommand
$Result | Add-Member -MemberType NoteProperty -Value $_.rundate -Name RunDate
$Result | Add-Member -MemberType NoteProperty -Value $ObjModified -Name ObjectModified
$Result | Add-Member -MemberType NoteProperty -Value $Property -Name ModifiedProperties
$Result | Add-Member -MemberType NoteProperty -Value $Oldvalue -Name OldValue
$Result | Add-Member -MemberType NoteProperty -Value $NewValue -Name NewValue

# Add the object to the array to be returned
$ResultSet = $ResultSet + $Result
}
# Final steps
End {
# Return the array set
Return $ResultSet
}
22 changes: 22 additions & 0 deletions docs/Admin/Get-SimpleAdminAuditLogReport.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
title: Get-SimpleAdminAuditLogReport.ps1
parent: Admin
---

## Get-SimpleAdminAuditLog

Download the latest release: [Get-SimpleAdminAuditLogReport.ps1](https://github.com/microsoft/CSS-Exchange/releases/latest/download/Get-SimpleAdminAuditLogReport.ps1)

Exchange admin audit logs are not readily human readable. All of the data needed to understand what Cmdlet has been run is in the data but it is not very easy to read. Get-SimpleAdminAuditLog will take the results of an audit log search and provide a significantly more human readable version of the data.

It will parse the audit log and attempt to reconstruct the actual Cmdlet that was run.

# Common Usage
`$Search = Search-AdminAuditLog`

`$search | C:\Scripts\Get-SimpleAuditLogReport.ps1 -agree`

# How to use
1. Gather admin audit log results using [Search-AdminAuditLog](https://docs.microsoft.com/en-us/powershell/module/exchange/search-adminauditlog?view=exchange-ps).
2. Pipe the results into the Get-SimpleAuditLogReport script.
3. Open the CSV file created in the same directory with the script.
4 changes: 4 additions & 0 deletions docs/Admin/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
title: Admin
has_children: true
---
1 change: 1 addition & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ ExchangeMitigations.ps1 | [Docs](Security/ExchangeMitigations) | [Download](http
ExPerfAnalyzer.ps1 | [Docs](Performance/ExPerfAnalyzer) | [Download](https://github.com/microsoft/CSS-Exchange/releases/latest/download/ExPerfAnalyzer.ps1)
FixInstallerCache | [Docs](Setup/FixInstallerCache) | [Download](https://github.com/microsoft/CSS-Exchange/releases/latest/download/FixInstallerCache.ps1)
Get-MRMDetails.ps1 | [Docs](Retention/Get-MRMDetails) | [Download](https://github.com/microsoft/CSS-Exchange/releases/latest/download/Get-MRMDetails.ps1)
Get-SimpleAdminAuditLogReport.ps1 | [Docs](Admin/Get-SimpleAdminAuditLogReport) | [Download](https://github.com/microsoft/CSS-Exchange/releases/latest/download/Get-SimpleAdminAuditLogReport.ps1)
HealthChecker.ps1 | [Docs](Diagnostics/HealthChecker) | [Download](https://github.com/microsoft/CSS-Exchange/releases/latest/download/HealthChecker.ps1)
http-vuln-cve2021-26855.nse | [Docs](Security/http-vuln-cve2021-26855) | [Download](https://github.com/microsoft/CSS-Exchange/releases/latest/download/http-vuln-cve2021-26855.nse)
SetupAssist.ps1 | [Docs](Setup/SetupAssist) | [Download](https://github.com/microsoft/CSS-Exchange/releases/latest/download/SetupAssist.ps1)
Expand Down