Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Promote the win_eventlog Module from community #699

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
287 changes: 287 additions & 0 deletions plugins/modules/win_eventlog.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,287 @@
#!powershell

# Copyright: (c) 2017, Andrew Saraceni <[email protected]>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)

#Requires -Module Ansible.ModuleUtils.Legacy

$ErrorActionPreference = "Stop"

function Get-EventLogDetail {
<#
.SYNOPSIS
Get details of an event log, sources, and associated attributes.
Used for comparison against passed-in option values to ensure idempotency.
#>
param(
[String]$LogName
)

$log_details = @{}
$log_details.name = $LogName
$log_details.exists = $false
$log = Get-EventLog -List | Where-Object { $_.Log -eq $LogName }

if ($log) {
$log_details.exists = $true
$log_details.maximum_size_kb = $log.MaximumKilobytes
$log_details.overflow_action = $log.OverflowAction.ToString()
$log_details.retention_days = $log.MinimumRetentionDays
$log_details.entries = $log.Entries.Count
$log_details.sources = [Ordered]@{}

# Retrieve existing sources and category/message/parameter file locations
# Associating file locations and sources with logs can only be done from the registry

$root_key = "HKLM:\SYSTEM\CurrentControlSet\Services\EventLog\{0}" -f $LogName
$log_root = Get-ChildItem -LiteralPath $root_key

foreach ($child in $log_root) {
$source_name = $child.PSChildName
$log_details.sources.$source_name = @{}
$hash_cursor = $log_details.sources.$source_name

$source_root = "{0}\{1}" -f $root_key, $source_name
$resource_files = Get-ItemProperty -LiteralPath $source_root

$hash_cursor.category_file = $resource_files.CategoryMessageFile
$hash_cursor.message_file = $resource_files.EventMessageFile
$hash_cursor.parameter_file = $resource_files.ParameterMessageFile
}
}

return $log_details
}

function Test-SourceExistence {
<#
.SYNOPSIS
Get information on a source's existence.
Examine existence regarding the parent log it belongs to and its expected state.
#>
param(
[String]$LogName,
[String]$SourceName,
[Switch]$NoLogShouldExist
)

$source_exists = [System.Diagnostics.EventLog]::SourceExists($SourceName)

if ($source_exists -and $NoLogShouldExist) {
Fail-Json -obj $result -message "Source $SourceName already exists and cannot be created"
}
elseif ($source_exists) {
$source_log = [System.Diagnostics.EventLog]::LogNameFromSourceName($SourceName, ".")
if ($source_log -ne $LogName) {
Fail-Json -obj $result -message "Source $SourceName does not belong to log $LogName and cannot be modified"
}
}

return $source_exists
}

function ConvertTo-MaximumSize {
<#
.SYNOPSIS
Convert a string KB/MB/GB value to common bytes and KB representations.
.NOTES
Size must be between 64KB and 4GB and divisible by 64KB, as per the MaximumSize parameter of Limit-EventLog.
#>
param(
[String]$Size
)

$parsed_size = @{
bytes = $null
KB = $null
}

$size_regex = "^\d+(\.\d+)?(KB|MB|GB)$"
if ($Size -notmatch $size_regex) {
Fail-Json -obj $result -message "Maximum size $Size is not properly specified"
}

$size_upper = $Size.ToUpper()
$size_numeric = [Double]$Size.Substring(0, $Size.Length - 2)

if ($size_upper.EndsWith("GB")) {
$size_bytes = $size_numeric * 1GB
}
elseif ($size_upper.EndsWith("MB")) {
$size_bytes = $size_numeric * 1MB
}
elseif ($size_upper.EndsWith("KB")) {
$size_bytes = $size_numeric * 1KB
}

if (($size_bytes -lt 64KB) -or ($size_bytes -ge 4GB)) {
Fail-Json -obj $result -message "Maximum size must be between 64KB and 4GB"
}
elseif (($size_bytes % 64KB) -ne 0) {
Fail-Json -obj $result -message "Maximum size must be divisible by 64KB"
}

$parsed_size.bytes = $size_bytes
$parsed_size.KB = $size_bytes / 1KB
return $parsed_size
}

$params = Parse-Args $args -supports_check_mode $true
$check_mode = Get-AnsibleParam -obj $params -name "_ansible_check_mode" -type "bool" -default $false

$name = Get-AnsibleParam -obj $params -name "name" -type "str" -failifempty $true
$state = Get-AnsibleParam -obj $params -name "state" -type "str" -default "present" -validateset "present", "clear", "absent"
$sources = Get-AnsibleParam -obj $params -name "sources" -type "list"
$category_file = Get-AnsibleParam -obj $params -name "category_file" -type "path"
$message_file = Get-AnsibleParam -obj $params -name "message_file" -type "path"
$parameter_file = Get-AnsibleParam -obj $params -name "parameter_file" -type "path"
$maximum_size = Get-AnsibleParam -obj $params -name "maximum_size" -type "str"
$overflow_action = Get-AnsibleParam -obj $params -name "overflow_action" -type "str" -validateset "OverwriteOlder", "OverwriteAsNeeded", "DoNotOverwrite"
$retention_days = Get-AnsibleParam -obj $params -name "retention_days" -type "int"

$result = @{
changed = $false
name = $name
sources_changed = @()
}

$log_details = Get-EventLogDetail -LogName $name

# Handle common error cases up front
if ($state -eq "present" -and !$log_details.exists -and !$sources) {
# When creating a log, one or more sources must be passed
Fail-Json -obj $result -message "You must specify one or more sources when creating a log for the first time"
}
elseif ($state -eq "present" -and $log_details.exists -and $name -in $sources -and ($category_file -or $message_file -or $parameter_file)) {
# After a default source of the same name is created, it cannot be modified without removing the log
Fail-Json -obj $result -message "Cannot modify default source $name of log $name - you must remove the log"
}
elseif ($state -eq "clear" -and !$log_details.exists) {
Fail-Json -obj $result -message "Cannot clear log $name as it does not exist"
}
elseif ($state -eq "absent" -and $name -in $sources) {
# You also cannot remove a default source for the log - you must remove the log itself
Fail-Json -obj $result -message "Cannot remove default source $name from log $name - you must remove the log"
}

try {
switch ($state) {
"present" {
foreach ($source in $sources) {
if ($log_details.exists) {
$source_exists = Test-SourceExistence -LogName $name -SourceName $source
}
else {
$source_exists = Test-SourceExistence -LogName $name -SourceName $source -NoLogShouldExist
}

if ($source_exists) {
$category_change = $category_file -and $log_details.sources.$source.category_file -ne $category_file
$message_change = $message_file -and $log_details.sources.$source.message_file -ne $message_file
$parameter_change = $parameter_file -and $log_details.sources.$source.parameter_file -ne $parameter_file
# Remove source and recreate later if any of the above are true
if ($category_change -or $message_change -or $parameter_change) {
Remove-EventLog -Source $source -WhatIf:$check_mode
}
else {
continue
}
}

$new_params = @{
LogName = $name
Source = $source
}
if ($category_file) {
$new_params.CategoryResourceFile = $category_file
}
if ($message_file) {
$new_params.MessageResourceFile = $message_file
}
if ($parameter_file) {
$new_params.ParameterResourceFile = $parameter_file
}

if (!$check_mode) {
New-EventLog @new_params
$result.sources_changed += $source
}
$result.changed = $true
}

if ($maximum_size) {
$converted_size = ConvertTo-MaximumSize -Size $maximum_size
}

$size_change = $maximum_size -and $log_details.maximum_size_kb -ne $converted_size.KB
$overflow_change = $overflow_action -and $log_details.overflow_action -ne $overflow_action
$retention_change = $retention_days -and $log_details.retention_days -ne $retention_days

if ($size_change -or $overflow_change -or $retention_change) {
$limit_params = @{
LogName = $name
WhatIf = $check_mode
}
if ($maximum_size) {
$limit_params.MaximumSize = $converted_size.bytes
}
if ($overflow_action) {
$limit_params.OverflowAction = $overflow_action
}
if ($retention_days) {
$limit_params.RetentionDays = $retention_days
}

Limit-EventLog @limit_params
$result.changed = $true
}

}
"clear" {
if ($log_details.entries -gt 0) {
Clear-EventLog -LogName $name -WhatIf:$check_mode
$result.changed = $true
}
}
"absent" {
if ($sources -and $log_details.exists) {
# Since sources were passed, remove sources tied to event log
foreach ($source in $sources) {
$source_exists = Test-SourceExistence -LogName $name -SourceName $source
if ($source_exists) {
Remove-EventLog -Source $source -WhatIf:$check_mode
if (!$check_mode) {
$result.sources_changed += $source
}
$result.changed = $true
}
}
}
elseif ($log_details.exists) {
# Only name passed, so remove event log itself (which also removes contained sources)
Remove-EventLog -LogName $name -WhatIf:$check_mode
if (!$check_mode) {
$log_details.sources.GetEnumerator() | ForEach-Object { $result.sources_changed += $_.Name }
}
$result.changed = $true
}
}
}
}
catch {
Fail-Json -obj $result -message $_.Exception.Message
}

$final_log_details = Get-EventLogDetail -LogName $name
foreach ($final_log_detail in $final_log_details.GetEnumerator()) {
if ($final_log_detail.Name -eq "sources") {
$sources = @()
$final_log_detail.Value.GetEnumerator() | ForEach-Object { $sources += $_.Name }
$result.$($final_log_detail.Name) = [Array]$sources
}
else {
$result.$($final_log_detail.Name) = $final_log_detail.Value
}
}

Exit-Json -obj $result
Loading