diff --git a/doc/31-Changelog.md b/doc/31-Changelog.md index 1a0141f..9a2997a 100644 --- a/doc/31-Changelog.md +++ b/doc/31-Changelog.md @@ -23,6 +23,7 @@ Released closed milestones can be found on [GitHub](https://github.com/Icinga/ic * [#335](https://github.com/Icinga/icinga-powershell-framework/pull/335) Fixes Icinga Director Self-Service Zones and CA config for legacy installation wizard * [#343](https://github.com/Icinga/icinga-powershell-framework/pull/343) Fixes freeze within Icinga Management Console, in case commands which previously existed were removed/renamed or the user applied an invalid configuration with unknown commands as install file or install command * [#345](https://github.com/Icinga/icinga-powershell-framework/pull/345) Fixes Framework environment variables like `$IcingaEnums` not working with v1.6.0 +* [#351](https://github.com/Icinga/icinga-powershell-framework/pull/351) Fixes file writer which could cause corruption on parallel read/write events on the same file ### Enhancements diff --git a/icinga-powershell-framework.psd1 b/icinga-powershell-framework.psd1 index 12472e7..fb2696b 100644 --- a/icinga-powershell-framework.psd1 +++ b/icinga-powershell-framework.psd1 @@ -11,9 +11,9 @@ FunctionsToExport = @( '*' ) CmdletsToExport = @( '*' ) VariablesToExport = '*' - AliasesToExport = @( 'icinga' ) + AliasesToExport = @( '*' ) PrivateData = @{ - PSData = @{ + PSData = @{ Tags = @( 'icinga', 'icinga2', 'IcingaPowerShellFramework', 'IcingaPowerShell', 'IcingaforWindows', 'IcingaWindows') LicenseUri = 'https://github.com/Icinga/icinga-powershell-framework/blob/master/LICENSE' ProjectUri = 'https://github.com/Icinga/icinga-powershell-framework' diff --git a/icinga-powershell-framework.psm1 b/icinga-powershell-framework.psm1 index 469dfb4..7cef110 100644 --- a/icinga-powershell-framework.psm1 +++ b/icinga-powershell-framework.psm1 @@ -119,7 +119,7 @@ function Write-IcingaFrameworkCodeCache() $CacheContent += "`r`n"; } - $CacheContent += "Export-ModuleMember -Function @( '*' )"; + $CacheContent += "Export-ModuleMember -Function @( '*' ) -Alias @( '*' ) -Variable @( '*' )"; Set-Content -Path $CacheFile -Value $CacheContent; } @@ -157,7 +157,7 @@ function Publish-IcingaEventlogDocumentation() if ([string]::IsNullOrEmpty($OutFile)) { Write-Output $DocContent; } else { - Set-Content -Path $OutFile -Value $DocContent; + Write-IcingaFileSecure -File $OutFile -Value $DocContent; } } diff --git a/lib/config/Read-IcingaPowerShellConfig.psm1 b/lib/config/Read-IcingaPowerShellConfig.psm1 index 8395152..370b0c4 100644 --- a/lib/config/Read-IcingaPowerShellConfig.psm1 +++ b/lib/config/Read-IcingaPowerShellConfig.psm1 @@ -15,24 +15,26 @@ function Read-IcingaPowerShellConfig() { - $ConfigDir = Get-IcingaPowerShellConfigDir; - $ConfigFile = Join-Path -Path $ConfigDir -ChildPath 'config.json'; + $ConfigDir = Get-IcingaPowerShellConfigDir; + $ConfigFile = Join-Path -Path $ConfigDir -ChildPath 'config.json'; + $ConfigObject = (New-Object -TypeName PSObject); + [string]$Content = Read-IcingaFileSecure -File $ConfigFile -ExitOnReadError; - if ($global:IcingaDaemonData.FrameworkRunningAsDaemon) { - if ($global:IcingaDaemonData.ContainsKey('Config')) { - return $global:IcingaDaemonData.Config; + if ([string]::IsNullOrEmpty($Content) -eq $FALSE) { + try { + $ConfigObject = (ConvertFrom-Json -InputObject $Content -ErrorAction Stop); + } catch { + New-Item -ItemType Directory -Path (Join-Path -Path $ConfigDir -ChildPath 'corrupt') -ErrorAction SilentlyContinue; + $NewConfigFile = Join-Path -Path $ConfigDir -ChildPath ([string]::Format('corrupt/config_broken_{0}.json', (Get-Date -Format "yyyy-MM-dd-HH-mm-ss-ffff"))); + Move-Item -Path $ConfigFile -Destination $NewConfigFile -ErrorAction SilentlyContinue; + New-Item -ItemType File -Path $ConfigFile -ErrorAction SilentlyContinue; + + Write-IcingaEventMessage -EventId 1100 -Namespace 'Framework' -Objects $ConfigFile, $Content; + Write-IcingaConsoleError -Message 'Your configuration file "{0}" was corrupt and could not be read. It was moved to "{1}" for review and a new plain file has been created' -Objects $ConfigFile, $NewConfigFile; + + $ConfigObject = (New-Object -TypeName PSObject); } } - if (-Not (Test-Path $ConfigFile)) { - return (New-Object -TypeName PSObject); - } - - [string]$Content = Read-IcingaFileContent -File $ConfigFile; - - if ([string]::IsNullOrEmpty($Content)) { - return (New-Object -TypeName PSObject); - } - - return (ConvertFrom-Json -InputObject $Content); + return $ConfigObject; } diff --git a/lib/config/Write-IcingaPowerShellConfig.psm1 b/lib/config/Write-IcingaPowerShellConfig.psm1 index 5375c8d..aba993a 100644 --- a/lib/config/Write-IcingaPowerShellConfig.psm1 +++ b/lib/config/Write-IcingaPowerShellConfig.psm1 @@ -17,7 +17,7 @@ function Write-IcingaPowerShellConfig() { - param( + param ( $Config ); @@ -25,16 +25,10 @@ function Write-IcingaPowerShellConfig() $ConfigFile = Join-Path -Path $ConfigDir -ChildPath 'config.json'; if (-Not (Test-Path $ConfigDir)) { - New-Item -Path $ConfigDir -ItemType Directory | Out-Null; + New-Item -Path $ConfigDir -ItemType Directory -ErrorAction SilentlyContinue | Out-Null; } $Content = ConvertTo-Json -InputObject $Config -Depth 100; - Set-Content -Path $ConfigFile -Value $Content; - - if ($global:IcingaDaemonData.FrameworkRunningAsDaemon) { - if ($global:IcingaDaemonData.ContainsKey('Config')) { - $global:IcingaDaemonData.Config = $Config; - } - } + Write-IcingaFileSecure -File $ConfigFile -Value $Content; } diff --git a/lib/core/cache/Get-IcingaCacheData.psm1 b/lib/core/cache/Get-IcingaCacheData.psm1 index 7d3882d..1b779fd 100644 --- a/lib/core/cache/Get-IcingaCacheData.psm1 +++ b/lib/core/cache/Get-IcingaCacheData.psm1 @@ -40,7 +40,7 @@ function Get-IcingaCacheData() return $null; } - $Content = Read-IcingaFileContent -File $CacheFile; + $Content = Read-IcingaFileSecure -File $CacheFile; if ([string]::IsNullOrEmpty($Content)) { return $null; diff --git a/lib/core/cache/Set-IcingaCacheData.psm1 b/lib/core/cache/Set-IcingaCacheData.psm1 index e3ca7ac..a6db939 100644 --- a/lib/core/cache/Set-IcingaCacheData.psm1 +++ b/lib/core/cache/Set-IcingaCacheData.psm1 @@ -53,17 +53,12 @@ function Set-IcingaCacheData() $KeyName = $Value }; } else { - if ($cacheData.PSobject.Properties.Name -ne $KeyName) { + if ($cacheData.PSObject.Properties.Name -ne $KeyName) { $cacheData | Add-Member -MemberType NoteProperty -Name $KeyName -Value $Value -Force; } else { $cacheData.$KeyName = $Value; } } - try { - Set-Content -Path $CacheFile -Value (ConvertTo-Json -InputObject $cacheData -Depth 100) | Out-Null; - } catch { - Exit-IcingaThrowException -InputString $_.Exception -CustomMessage (Get-IcingaCacheDir) -StringPattern 'System.UnauthorizedAccessException' -ExceptionType 'Permission' -ExceptionThrown $IcingaExceptions.Permission.CacheFolder; - Exit-IcingaThrowException -CustomMessage $_.Exception -ExceptionType 'Unhandled' -Force; - } -} \ No newline at end of file + Write-IcingaFileSecure -File $CacheFile -Value (ConvertTo-Json -InputObject $cacheData -Depth 100); +} diff --git a/lib/core/icingaagent/finders/Find-IcingaAgentObjects.psm1 b/lib/core/icingaagent/finders/Find-IcingaAgentObjects.psm1 index 8b74c7b..2988b5d 100644 --- a/lib/core/icingaagent/finders/Find-IcingaAgentObjects.psm1 +++ b/lib/core/icingaagent/finders/Find-IcingaAgentObjects.psm1 @@ -35,6 +35,6 @@ function Find-IcingaAgentObjects() if ([string]::IsNullOrEmpty($OutFile)) { Write-Output $Result; } else { - Set-Content -Path $OutFile -Value $Result; + Write-IcingaFileSecure -File $OutFile -Value $Result; } } diff --git a/lib/core/icingaagent/installer/Install-IcingaAgentCertificates.psm1 b/lib/core/icingaagent/installer/Install-IcingaAgentCertificates.psm1 index 6329908..86467ac 100644 --- a/lib/core/icingaagent/installer/Install-IcingaAgentCertificates.psm1 +++ b/lib/core/icingaagent/installer/Install-IcingaAgentCertificates.psm1 @@ -279,7 +279,7 @@ function Copy-IcingaAgentCACertificate() $Index, $response.RawContent.Length - $Index ); - Set-Content -Path (Join-Path $Desination -ChildPath 'ca.crt') -Value $CAContent; + Write-IcingaFileSecure -File (Join-Path $Desination -ChildPath 'ca.crt') -Value $CAContent; Write-IcingaConsoleNotice ([string]::Format('Downloaded ca.crt from "{0}" to "{1}', $CAPath, $Desination)) } catch { Write-IcingaConsoleError 'Failed to load any provided ca.crt ressource'; diff --git a/lib/core/icingaagent/setters/Set-IcingaAgentNodeName.psm1 b/lib/core/icingaagent/setters/Set-IcingaAgentNodeName.psm1 index 6262823..e9837f5 100644 --- a/lib/core/icingaagent/setters/Set-IcingaAgentNodeName.psm1 +++ b/lib/core/icingaagent/setters/Set-IcingaAgentNodeName.psm1 @@ -29,7 +29,7 @@ function Set-IcingaAgentNodeName() $ConfigContent = $NewConfigContent; } - Set-Content -Path $ConstantsConf -Value $ConfigContent; + Write-IcingaFileSecure -File $ConstantsConf -Value $ConfigContent; Write-IcingaConsoleNotice ([string]::Format('Your hostname was successfully changed to "{0}"', $Hostname)); } diff --git a/lib/core/icingaagent/setters/Set-IcingaAgentServicePermission.psm1 b/lib/core/icingaagent/setters/Set-IcingaAgentServicePermission.psm1 index 8e19ece..f3e1033 100644 --- a/lib/core/icingaagent/setters/Set-IcingaAgentServicePermission.psm1 +++ b/lib/core/icingaagent/setters/Set-IcingaAgentServicePermission.psm1 @@ -24,7 +24,7 @@ function Set-IcingaAgentServicePermission() $NewSystemContent += $line; } - Set-Content -Path "$SystemPermissions.inf" -Value $NewSystemContent; + Write-IcingaFileSecure -File "$SystemPermissions.inf" -Value $NewSystemContent; $SystemOutput = Start-IcingaProcess -Executable 'secedit.exe' -Arguments ([string]::Format('/import /cfg "{0}.inf" /db "{0}.sdb"', $SystemPermissions)); diff --git a/lib/core/icingaagent/writers/Write-IcingaAgentApiConfig.psm1 b/lib/core/icingaagent/writers/Write-IcingaAgentApiConfig.psm1 index 1f577a6..9099ecc 100644 --- a/lib/core/icingaagent/writers/Write-IcingaAgentApiConfig.psm1 +++ b/lib/core/icingaagent/writers/Write-IcingaAgentApiConfig.psm1 @@ -15,6 +15,6 @@ function Write-IcingaAgentApiConfig() $ApiConf = $ApiConf.Substring(0, $ApiConf.Length - 4); - Set-Content -Path (Join-Path -Path (Get-IcingaAgentConfigDirectory) -ChildPath 'features-available\api.conf') -Value $ApiConf; + Write-IcingaFileSecure -File (Join-Path -Path (Get-IcingaAgentConfigDirectory) -ChildPath 'features-available\api.conf') -Value $ApiConf; Write-IcingaConsoleNotice 'Api configuration has been written successfully'; } diff --git a/lib/core/icingaagent/writers/Write-IcingaAgentObjectList.psm1 b/lib/core/icingaagent/writers/Write-IcingaAgentObjectList.psm1 index ea2615b..6334066 100644 --- a/lib/core/icingaagent/writers/Write-IcingaAgentObjectList.psm1 +++ b/lib/core/icingaagent/writers/Write-IcingaAgentObjectList.psm1 @@ -10,5 +10,5 @@ function Write-IcingaAgentObjectList() $ObjectList = Get-IcingaAgentObjectList; - Set-Content -Path $Path -Value $ObjectList; + Write-IcingaFileSecure -File $Path -Value $ObjectList; } diff --git a/lib/core/icingaagent/writers/Write-IcingaAgentZonesConfig.psm1 b/lib/core/icingaagent/writers/Write-IcingaAgentZonesConfig.psm1 index 181ab4e..7bd5eb0 100644 --- a/lib/core/icingaagent/writers/Write-IcingaAgentZonesConfig.psm1 +++ b/lib/core/icingaagent/writers/Write-IcingaAgentZonesConfig.psm1 @@ -66,6 +66,6 @@ function Write-IcingaAgentZonesConfig() $ZonesConf = $ZonesConf.Substring(0, $ZonesConf.Length - 4); - Set-Content -Path (Join-Path -Path (Get-IcingaAgentConfigDirectory) -ChildPath 'zones.conf') -Value $ZonesConf; + Write-IcingaFileSecure -File (Join-Path -Path (Get-IcingaAgentConfigDirectory) -ChildPath 'zones.conf') -Value $ZonesConf; Write-IcingaConsoleNotice 'Icinga Agent zones.conf has been written successfully'; } diff --git a/lib/core/installer/Install-Icinga.psm1 b/lib/core/installer/Install-Icinga.psm1 index 994730e..6de0164 100644 --- a/lib/core/installer/Install-Icinga.psm1 +++ b/lib/core/installer/Install-Icinga.psm1 @@ -41,7 +41,7 @@ function Install-Icinga() } if ([string]::IsNullOrEmpty($InstallFile) -eq $FALSE) { - $InstallCommand = Read-IcingaFileContent -File $InstallFile; + $InstallCommand = Read-IcingaFileSecure -File $InstallFile -ExitOnReadError; } # Use our install command to configure everything diff --git a/lib/core/installer/menu/installation/general/InstallationAnswerFile.psm1 b/lib/core/installer/menu/installation/general/InstallationAnswerFile.psm1 index c2f520b..57c216e 100644 --- a/lib/core/installer/menu/installation/general/InstallationAnswerFile.psm1 +++ b/lib/core/installer/menu/installation/general/InstallationAnswerFile.psm1 @@ -8,7 +8,7 @@ function Export-IcingaForWindowsManagementConsoleInstallationAnswerFile() } if (Test-Path ($FilePath)) { - Set-Content -Path (Join-Path -Path $FilePath -ChildPath 'IfW_answer.json') -Value (Get-IcingaForWindowsManagementConsoleConfigurationString); + Write-IcingaFileSecure -File (Join-Path -Path $FilePath -ChildPath 'IfW_answer.json') -Value (Get-IcingaForWindowsManagementConsoleConfigurationString); $global:Icinga.InstallWizard.NextCommand = 'Install-Icinga'; $global:Icinga.InstallWizard.LastNotice = ([string]::Format('Answer file "IfW_answer.json" successfully exported into "{0}"', $FilePath)); Clear-IcingaForWindowsManagementConsolePaginationCache; diff --git a/lib/core/logging/Icinga_EventLog_Enums.psm1 b/lib/core/logging/Icinga_EventLog_Enums.psm1 index 0e76747..9b9604b 100644 --- a/lib/core/logging/Icinga_EventLog_Enums.psm1 +++ b/lib/core/logging/Icinga_EventLog_Enums.psm1 @@ -14,6 +14,24 @@ if ($null -eq $IcingaEventLogEnums -Or $IcingaEventLogEnums.ContainsKey('Framewo 'Details' = 'The Framework or is components can issue generic debug message in case the debug log is enabled. Please ensure to disable it, if not used. You can do so with the command "Disable-IcingaFrameworkDebugMode"'; 'EventId' = 1000; }; + 1100 = @{ + 'EntryType' = 'Error'; + 'Message' = 'Corrupt Icinga for Windows configuration'; + 'Details' = 'Your Icinga for Windows configuration file was corrupt and could not be read successfully. A new configuration file was created and the old one renamed for review, to keep your settings available.'; + 'EventId' = 1100; + }; + 1101 = @{ + 'EntryType' = 'Warning'; + 'Message' = 'Unable to update Icinga for Windows file'; + 'Details' = 'Icinga for Windows could not update the specified file after several attempts, because another process is locking it. Modifications made on the file have not been persisted.'; + 'EventId' = 1101; + }; + 1102 = @{ + 'EntryType' = 'Warning'; + 'Message' = 'Unable to read Icinga for Windows content file'; + 'Details' = 'Icinga for Windows could not read the specified file after several attempts, because another process is locking the file. Icinga for Windows terminated itself to prevent damage to this file.'; + 'EventId' = 1102; + }; 1500 = @{ 'EntryType' = 'Error'; 'Message' = 'Failed to securely establish a communication between this server and the client'; diff --git a/lib/core/repository/New-IcingaRepositoryFile.psm1 b/lib/core/repository/New-IcingaRepositoryFile.psm1 index 11320f8..f5f1763 100644 --- a/lib/core/repository/New-IcingaRepositoryFile.psm1 +++ b/lib/core/repository/New-IcingaRepositoryFile.psm1 @@ -85,7 +85,7 @@ function New-IcingaRepositoryFile() $IcingaRepository.Info.RepoHash = Get-IcingaRepositoryHash -Path $Path; } - Set-Content -Path $RepoPath -Value (ConvertTo-Json -InputObject $IcingaRepository -Depth 100); + Write-IcingaFileSecure -File $RepoPath -Value (ConvertTo-Json -InputObject $IcingaRepository -Depth 100); return $IcingaRepository; } diff --git a/lib/core/repository/Sync-IcingaRepository.psm1 b/lib/core/repository/Sync-IcingaRepository.psm1 index 8d9afab..52bc95f 100644 --- a/lib/core/repository/Sync-IcingaRepository.psm1 +++ b/lib/core/repository/Sync-IcingaRepository.psm1 @@ -194,7 +194,7 @@ function Sync-IcingaRepository() $JsonRepo.Info.RemoteSource = $RemotePath; $JsonRepo.Info.Updated = ((Get-Date).ToUniversalTime().ToString('yyyy\/MM\/dd HH:mm:ss')); - Set-Content -Path $RepoFile -Value (ConvertTo-Json -InputObject $JsonRepo -Depth 100); + Write-IcingaFileSecure -File $RepoFile -Value (ConvertTo-Json -InputObject $JsonRepo -Depth 100); if ($UseSCP -eq $FALSE) { # Windows target $Success = Remove-Item -Path $RemovePath -Recurse -Force; diff --git a/lib/core/tools/Get-IcingaCheckCommandConfig.psm1 b/lib/core/tools/Get-IcingaCheckCommandConfig.psm1 index e94df5e..20affe5 100644 --- a/lib/core/tools/Get-IcingaCheckCommandConfig.psm1 +++ b/lib/core/tools/Get-IcingaCheckCommandConfig.psm1 @@ -408,7 +408,7 @@ function Get-IcingaCheckCommandConfig() if ($IcingaConfig) { Write-IcingaPlainConfigurationFiles -Content $Basket -OutDirectory $ConfigDirectory -FileName $FileName; } else { - Set-Content -Path $OutDirectory -Value $output; + Write-IcingaFileSecure -File $OutDirectory -Value $output; } # Output-Text @@ -571,8 +571,8 @@ function Write-IcingaPlainConfigurationFiles() $PowerShellBase += [string]::Format(' timeout = 3m{0}', (New-IcingaNewLine)); $PowerShellBase += '}'; - Set-Content -Path (Join-Path -Path $ConfigDirectory -ChildPath 'PowerShell_Base.conf') -Value $PowerShellBase; - Set-Content -Path $OutDirectory -Value $IcingaConfig; + Write-IcingaFileSecure -File (Join-Path -Path $ConfigDirectory -ChildPath 'PowerShell_Base.conf') -Value $PowerShellBase; + Write-IcingaFileSecure -File $OutDirectory -Value $IcingaConfig; } function Add-PowerShellDataList() diff --git a/lib/core/tools/Read-IcingaFileContent.psm1 b/lib/core/tools/Read-IcingaFileContent.psm1 deleted file mode 100644 index 9008b20..0000000 --- a/lib/core/tools/Read-IcingaFileContent.psm1 +++ /dev/null @@ -1,44 +0,0 @@ -<# -.SYNOPSIS - Reads content of a file in read-only mode, ensuring no data corruption is happening -.DESCRIPTION - Reads content of a file in read-only mode, ensuring no data corruption is happening -.FUNCTIONALITY - Reads content of a file in read-only mode, ensuring no data corruption is happening -.EXAMPLE - PS>Read-IcingaFileContent -File 'config.json'; -.OUTPUTS - System.Object -.LINK - https://github.com/Icinga/icinga-powershell-framework -#> - -function Read-IcingaFileContent() -{ - param ( - [string]$File - ); - - if ((Test-Path $File) -eq $FALSE) { - return $null; - } - - [System.IO.FileStream]$FileStream = [System.IO.File]::Open( - $File, - [System.IO.FileMode]::Open, - [System.IO.FileAccess]::Read, - [System.IO.FileShare]::Read - ); - - $ReadArray = New-Object Byte[] $FileStream.Length; - $UTF8Encoding = New-Object System.Text.UTF8Encoding $TRUE; - $FileContent = ''; - - while ($FileStream.Read($ReadArray, 0 , $ReadArray.Length)) { - $FileContent = [System.String]::Concat($FileContent, $UTF8Encoding.GetString($ReadArray)); - } - - $FileStream.Dispose(); - - return $FileContent; -} diff --git a/lib/core/tools/Read-IcingaFileSecure.psm1 b/lib/core/tools/Read-IcingaFileSecure.psm1 new file mode 100644 index 0000000..3b7cbe8 --- /dev/null +++ b/lib/core/tools/Read-IcingaFileSecure.psm1 @@ -0,0 +1,71 @@ +<# +.SYNOPSIS + Reads content of a file in read-only mode, ensuring no data corruption is happening +.DESCRIPTION + Reads content of a file in read-only mode, ensuring no data corruption is happening +.FUNCTIONALITY + Reads content of a file in read-only mode, ensuring no data corruption is happening +.EXAMPLE + PS>Read-IcingaFileSecure -File 'config.json'; +.EXAMPLE + PS>Read-IcingaFileSecure -File 'config.json' -ExitOnReadError; +.OUTPUTS + System.Object +.LINK + https://github.com/Icinga/icinga-powershell-framework +#> + +function Read-IcingaFileSecure() +{ + param ( + [string]$File, + [switch]$ExitOnReadError = $FALSE + ); + + if ([string]::IsNullOrEmpty($File) -Or (Test-Path $File) -eq $FALSE) { + return $null; + } + + [int]$WaitTicks = 0; + [bool]$ConfigRead = $FALSE; + + # Lets wait 5 seconds before cancelling reading + while ($WaitTicks -lt (($WaitTicks + 1) * 50)) { + try { + [System.IO.FileStream]$FileStream = [System.IO.File]::Open( + $File, + [System.IO.FileMode]::Open, + [System.IO.FileAccess]::Read, + [System.IO.FileShare]::Read + ); + + $ReadArray = New-Object Byte[] $FileStream.Length; + $UTF8Encoding = New-Object System.Text.UTF8Encoding $TRUE; + $FileContent = ''; + + while ($FileStream.Read($ReadArray, 0 , $ReadArray.Length)) { + $FileContent = [System.String]::Concat($FileContent, $UTF8Encoding.GetString($ReadArray)); + } + + $FileStream.Dispose(); + $ConfigRead = $TRUE; + break; + } catch { + # File is still locked, wait for lock to vanish + } + + $WaitTicks += 1; + Start-Sleep -Milliseconds 100; + } + + if ($ConfigRead -eq $FALSE -And $ExitOnReadError) { + Write-IcingaEventMessage -EventId 1102 -Namespace 'Framework' -Objects $ConfigFile, $Content; + Write-IcingaConsoleWarning -Message 'Your file "{0}" could not be read, as another process is locking it. Icinga for Windows will terminate itself after 5 seconds to prevent damage to this file.' -Objects $File; + Start-Sleep -Seconds 5; + exit 3; + } + + return $FileContent; +} + +Set-Alias -Name 'Read-IcingaFileContent' -Value 'Read-IcingaFileSecure'; diff --git a/lib/core/tools/Write-IcingaFileSecure.psm1 b/lib/core/tools/Write-IcingaFileSecure.psm1 new file mode 100644 index 0000000..6dee2ee --- /dev/null +++ b/lib/core/tools/Write-IcingaFileSecure.psm1 @@ -0,0 +1,39 @@ +function Write-IcingaFileSecure() +{ + param ( + [string]$File, + $Value + ); + + [int]$WaitTicks = 0; + [bool]$FileUpdated = $FALSE; + + # Lets wait 5 seconds before cancelling writing + while ($WaitTicks -lt (($WaitTicks + 1) * 50)) { + try { + [System.IO.FileStream]$FileStream = [System.IO.File]::Open( + $File, + [System.IO.FileMode]::Truncate, + [System.IO.FileAccess]::Write, + [System.IO.FileShare]::Read + ); + + $ContentBytes = [System.Text.Encoding]::UTF8.GetBytes($Value); + $FileStream.Write($ContentBytes, 0, $ContentBytes.Length); + $FileStream.Dispose(); + $FileUpdated = $TRUE; + break; + } catch { + Exit-IcingaThrowException -InputString $_.Exception -CustomMessage $File -StringPattern 'System.UnauthorizedAccessException' -ExceptionType 'Permission' -ExceptionThrown $IcingaExceptions.Permission.CacheFolder; + # File is still locked, wait for lock to vanish + } + + $WaitTicks += 1; + Start-Sleep -Milliseconds 100; + } + + if ($FileUpdated -eq $FALSE) { + Write-IcingaEventMessage -EventId 1101 -Namespace 'Framework' -Objects $File, $Value; + Write-IcingaConsoleWarning -Message 'Your file "{0}" could not be updated with your changes, as another process is locking it.' -Objects $File; + } +}