classes/New-BlockFactory.ps1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 |
function New-BlockFactory { ####################### # BlockFactory Class # ####################### # BlockFactory is a stateful factory that constructs Block Objects, a Configuration. It keeps a list of Blocks. $BlockFactory = [PSCustomObject]@{ 'Constants' = [scriptblock]{ # Constants $g_globaloptions_allowed_str = 'compress,compresscmd,uncompresscmd,compressext,compressoptions,uncompressoptions,copy,copytruncate,create,daily,dateext,dateformat,delaycompress,extension,ifempty,mail,mailfirst,maillast,maxage,minsize,missingok,monthly,nocompress,nocopy,nocopytruncate,nocreate,nodelaycompress,nodateext,nomail,nomissing,noolddir,nosharedscripts,noshred,notifempty,olddir,rotate,size,sharedscripts,shred,shredcycle,start,tabooext,weekly,yearly' $g_options_not_singleline_str = 'postrotate,prerotate,firstaction,lastaction,preremove'; $g_options_not_switches_str = 'compresscmd,uncompresscmd,compressext,compressoptions,uncompressoptions,create,dateformat,extension,include,mail,maxage,minsize,olddir,postrotate,prerotate,firstaction,lastaction,preremove,rotate,size,shredcycle,start,tabooext' # Constants as arrays [string[]]$g_globaloptions_allowed = $g_globaloptions_allowed_str.Split(',') [string[]]$g_options_not_singleline = $g_options_not_singleline_str.Split(','); [string[]]$g_localoptions_allowed = $g_globaloptions_allowed + $g_options_not_singleline [string[]]$g_options_not_switches = $g_options_not_switches_str.Split(',') # Define our config-capturing regexes [Regex]$g_localconfigs_regex = '([^\n]*)({(?:(?:(firstaction|lastaction|prerotate|postrotate|preremove)(?:\s|.)*?endscript)|[^}])*})' [Regex]$g_globaloptions_allowed_regex = "(?:^|\n)[^\S\n]*\b($( ($g_globaloptions_allowed -join '|') ))\b(.*)" [Regex]$g_localoptions_allowed_regex = "\n[^\S\n]*(?:\b($( ($g_globaloptions_allowed -join '|') ))\b(.*)|\b(postrotate|prerotate|firstaction|lastaction|preremove)[^\n]*\n((?:.|\s)*?)\n.*\b(endscript)\b)" [hashtable]$g_no_yes = @{ 'nocompress' = 'compress' 'nocopy' = 'copy' 'nocopytruncate' = 'copytruncate' 'nocreate' = 'create' 'nodelaycompress' = 'delaycompress' 'nodateext' = 'dateext' 'nomail' = 'mail' 'nomissingok' = 'missingok' 'notifempty' = 'ifempty' 'noolddir' = 'olddir' 'nosharedscripts' = 'sharedscripts' 'noshred' = 'shred' } #[Regex]$globalconfig_regex = '<?!#(' + ($g_globaloptions_allowed -join '|') + ')' } 'GlobalOptions' = @{ 'compresscmd' = "C:\Program Files\7-Zip\7z.exe" 'uncompresscmd' = "C:\Program Files\7-Zip\7z.exe" 'compressext' = '.7z' 'compressoptions' = 'a -t7z' 'uncompressoptions' = 'x -t7z' 'size' = '' 'dateformat' = '-%Y%m%d' 'nomissingok' = $true 'rotate' = 4 'start' = 1 'tabooext' = '.rpmorig, .rpmsave, .swp, .rpmnew, ~, .cfsaved, .rhn-cfg-tmp-*.' 'force' = $force } 'Blocks' = [ordered]@{} 'UniqueLogFileNames' = New-Object System.Collections.ArrayList 'PrivateMethods' = [scriptblock]{ function Get-Options { param ( [string]$configString, [hashtable]$options_found, [string[]]$options_allowed, [Regex]$options_allowed_regex, [string[]]$options_not_switches ) $matches = $options_allowed_regex.Matches($configString) if ($matches.success) { $matches | ForEach-Object { # Get key and value $match = $_ $key = if ($match.Groups[1].Value) { $match.Groups[1].Value } else { $match.Groups[3].Value } $value = if ($match.Groups[2].Value) { $match.Groups[2].Value } else { $match.Groups[4].Value } #Write-Verbose "`nLine: $line" #Write-Verbose "key: $key" #Write-Verbose "value: $value" #Write-Verbose "Contains: $($options_allowed.Contains($key))" # Store this option to hashtable. If there are duplicate options, later override earlier ones. if ($key) { if ($options_allowed.Contains($key)) { $options_found[$key] = if ($options_not_switches.Contains($key)) { # Don't trim if it's an option with a multiline value if (!$g_options_not_singleline.Contains($key)) { $value.Trim() }else { $value } } else { $true } } } } } #$options_found } function Override-Options { param ( [hashtable]$child, [hashtable]$parent ) # Override my parent options with my options $my_options = $parent.Clone() $child.GetEnumerator() | ForEach-Object { $key = $_.Name $value = $_.Value $my_options[$key] = $value } # When I said yes, and I didn't say no, but my parent said no, I will still go ahead. $g_no_yes.GetEnumerator() | ForEach-Object { $no = $_.Name $yes = $_.Value if ( $child.ContainsKey($yes) -and (!$child.ContainsKey($no)) -and $parent.ContainsKey($no) ) { $my_options.Remove($no) } } $my_options } # Returns an array of log files, that match a given blockpath pattern but whose fullpath is not already present in a unique store function Get-Block-Logs { param ([object]$blockObject) $blockpath = $blockObject['Path'] $opt_tabooext = $blockObject['Options']['tabooext'] $opt_missingok = if ($blockObject['Options']['notmissingok']) { $false } else { $blockObject['Options']['missingok'] } # Split the blockpath pattern by spaces, to get either 1) log paths or 2) wildcarded-paths $logpaths = [System.Collections.Arraylist]@() $matches = [Regex]::Matches($blockpath, '"([^"]+)"|([^\s]+)') if ($matches.success) { $matches | ForEach-Object { $path = if ($_.Groups[1].Value.Trim()) { $_.Groups[1].Value }else { $_.Groups[2].Value } if ($path) { $logpaths.Add($path) | Out-Null } } } # Get all the log files matching path patterns defined in the Config. $logfiles = New-Object System.Collections.Arraylist foreach ($logpath in $logpaths) { # Test if the path (without any wildcards) exists if ($logpath -match '\*') { # It's a wildcarded path. Write-Verbose "Considering wildcarded path $logpath" if (Test-Path -Path $logpath) { # Add all files that match the wildcard path $items = Get-ChildItem $logpath -File | Where-Object { (likeIn $_.Name $opt_tabooext.Split(',').Trim()) -eq $false } $items | ForEach-Object { $logfiles.Add($_) | Out-Null } }else { if (!$opt_missingok) { Write-Verbose "Excluding wildcarded path $logpath for rotation, because it doesn't exist!" } } }else { # It's a non-wildcarded path. Reject if it's a folder. if (Test-Path -LiteralPath $logpath) { $item = Get-Item -Path $logpath if (Test-Path $item.FullName -PathType Container) { # It's a directory. Ignore it. if (!$opt_missingok) { Write-Verbose "Excluding path $logpath for rotation, because it is a directory. Directories cannot be rotated. If rotating all the files in the directory, append a wildcard to the end of the path." } }else { # It's a file. Add $logfiles.Add($item) | Out-Null } }else { if (!$opt_missingok) { Write-Verbose "Excluding log $logpath for rotation, because it doesn't exist or does not point to file!" } } } } # Add unique log files to our list. If a logs already added, it must be a duplicate so we ignore it. if ($logfiles.Count) { $logfileCount = $logfiles.Count - 1 foreach ($i in (0..$logfileCount)) { $logfile = $logfiles[$i] if ($logfile.FullName -in $this.UniqueLogFileNames) { Write-Verbose "CONFIG: WARNING - Duplicate Log included: $($logfile.FullName) (matched in block pattern: $blockpath). Skipping rotation for this entry." $logfiles.Remove($logfile) }else { $this.UniqueLogFileNames.Add($logfile.FullName) | Out-Null } } } $logfiles } } } $BlockFactory | Add-Member -Name 'Create' -MemberType ScriptMethod -Value { param ([string]$FullConfig) # Unpack my properties . $this.Constants # Unpack my methods . $this.PrivateMethods # Parse Full Config for global options as hashtable $globalconfig = $g_localconfigs_regex.Replace($FullConfig, '') Get-Options $globalconfig $this.GlobalOptions $g_globaloptions_allowed $g_globaloptions_allowed_regex $g_options_not_switches # Parse Full Config for all found local block(s) path pattern, options, and matching log files, storing them as hashtable. Override the global options. # TODO: Regex for localconfigs to match paths on multiple lines before { } $matches = $g_localconfigs_regex.Matches($FullConfig) if ($matches.success) { foreach ($localconfig in $matches) { # NOTE: NOT USED ANYMORE: A block pattern should delimit multiple paths with a single space #$my_path_pattern = ($localconfig.Groups[1].Value -Split ' ' | Where-Object { $_.Trim() }).Trim() -join ' ' # Just get the raw path pattern $my_path_pattern = $localconfig.Groups[1].Value.Trim() if ($my_path_pattern -in $this.Blocks.Keys) { Write-Verbose "CONFIG: WARNING - Duplicate path pattern $my_path_pattern . Only the latest entry will be used." } # Any duplicate block path pattern overrides the previous $this.Blocks[$my_path_pattern] = @{ 'Path' = $my_path_pattern 'Options' = @{} 'LocalOptions' = @{} 'LogFiles' = '' } try { Get-Options $localconfig.Groups[2].Value $this.Blocks[$my_path_pattern]['LocalOptions'] $g_localoptions_allowed $g_localoptions_allowed_regex $g_options_not_switches $this.Blocks[$my_path_pattern]['Options'] = Override-Options $this.Blocks[$my_path_pattern]['LocalOptions'] $this.GlobalOptions $this.Blocks[$my_path_pattern]['LogFiles'] = Get-Block-Logs $this.Blocks[$my_path_pattern] $this.UniqueLogFileNames } catch { throw } } }else { Write-Verbose "CONFIG: WARNING - No configuration blocks were found." } } $BlockFactory | Add-Member -Name 'GetAll' -MemberType ScriptMethod -Value { $this.Blocks } $BlockFactory } |