A bit of a niche function this one. When you perform a mailbox export in Exchange using the New-MailboxExportRequest cmdlet you can specify the ContentFilter parameter. This allows you to pass an OPATH filter to only export SOME of the content from a mailbox.
One of the examples on the page above is;
New-MailboxExportRequest -Mailbox Tony -ContentFilter {(body -like “*company*”) -and (body -like “*profit*”) -and (Received -lt “01/01/2012”)}
So what if you need to check the syntax of a ContentFilter?
Most of the time, you’re just going to try and use the ContentFilter immediately; if the command fails then you know the filter was incorrect.
There may be times where the filter is specified long before it’s used though; maybe in a very long script, or where the filter is generated or read from a file.
Here’s a function that does a rudimentary check.
function Get-IsValidContentFilter { param ( [Parameter(Mandatory = $true)] [string]$ContentFilter ) [System.Collections.Hashtable]$ValidContentFilterFirstOperands=@{"All"="[String]";"Attachment"="[string]";"Body"="[string]";"Category"="[string]"; "Expires"="[DateTime]";"HasAttachment"="[boolean]";"Importance"=@("Low","Normal","High","0","1","2");"IsFlagged"="[boolean]"; "IsRead"="[boolean]";"MessageKind"=@("Email","Calendar","Task","Note","Doc","Journal","Contact","InstantMessage","Voicemail","Fax","Post","RSSFeed");"MessageLocale"="Culture";"PolicyTag"="[string]"; "Received"="[DateTime]";"Sent"="[DateTime]";"Size"="Size";"Subject"="[string]"} [string[]]$ValidContentFilterComparisonOperators=@("eq","ne","gt","lt","le","like","notlike","match","notmatch","contains","notcontains","replace") [string[]]$ValidContentFilterLogicalBinaryOperators=@("and","or","xor","not") Write-Verbose "BaseString: $ContentFilter" if($ContentFilter -match "\((?<FirstPart>.+)\) \-(?<Operator>.+) \((?<SecondPart>.+)\)") { $LogicalOperatorShouldBeUsed=$True }elseIf ($ContentFilter -match "\((?<InsideBrackets>.+)\)") { Get-IsValidContentFilter -ContentFilter $Matches.InsideBrackets.Trim() }elseIf ($ContentFilter -Match "(?<FirstPart>.+) \-(?<Operator>.+) (?<SecondPart>.+)") { $LogicalOperatorShouldBeUsed=$False }else { Return $False } $FirstOperand=$Matches.FirstPart.Trim() $Operator=$Matches.Operator.Trim() $SecondOperand=$Matches.SecondPart.Trim() Write-Verbose "$FirstOperand, $Operator, $SecondOperand" if ($LogicalOperatorShouldBeUsed) { $FirstPartIsValid=Get-IsValidContentFilter -ContentFilter $FirstOperand $SecondPartIsValid=Get-IsValidContentFilter -ContentFilter $SecondOperand $OperatorIsValid=$ValidContentFilterLogicalBinaryOperators -contains $Operator }else { $OperatorIsValid=$ValidContentFilterComparisonOperators -contains $Operator if (($ValidContentFilterFirstOperands.Keys -contains $FirstOperand)) { Write-Verbose "Matched First Operand Directly" $FirstPartIsValid=$True Write-Verbose "Testing 2nd Operand Type" $SecondPartIsValid=$False $TestingType=$ValidContentFilterFirstOperands.$FirstOperand Write-Verbose "Checking $FirstOperand" Write-Verbose "Corresponding Type: $TestingType" if ($TestingType -is [Array]) { Write-Verbose "Found Type Array: $TestingType" $SecondPartIsValid=[bool]([string[]]$TestingType -contains $SecondOperand) }else { switch -regex ($TestingType) { "\[(?<TypeName>.+)\]" { Write-Verbose "Found Type $TestingType" $SecondPartIsValid=[string]$SecondOperand -as $Matches.TypeName Continue } "Culture" { Write-Verbose "Found Culture Reference" $SecondPartIsValid=([globalization.cultureinfo]::GetCultures("allCultures") | Where-Object {$_}) -contains $SecondOperand Continue } } } }else { $FirstPartIsValid=$OperatorIsValid=$SecondPartIsValid=$False } } Write-Verbose "FirstOperand Valid: $FirstPartIsValid" Write-Verbose "Operator Valid: $OperatorIsValid" Write-Verbose "SecondOperand Valid: $SecondPartIsValid" $FirstPartIsValid -and $OperatorIsValid -and $SecondPartIsValid } }
The mailbox content filter has a few valid operands to use for testing (like “body” in the example. So the script lists them (with the standard operators like “eq”, “lt” etc). With each I also include what is valid to use them with as well (so “Received” must be used with a [DateTime] object;
[System.Collections.Hashtable]$ValidContentFilterFirstOperands=@{"All"="[String]";"Attachment"="[string]";"Body"="[string]";"Category"="[string]"; "Expires"="[DateTime]";"HasAttachment"="[boolean]";"Importance"=@("Low","Normal","High","0","1","2");"IsFlagged"="[boolean]"; "IsRead"="[boolean]";"MessageKind"=@("Email","Calendar","Task","Note","Doc","Journal","Contact","InstantMessage","Voicemail","Fax","Post","RSSFeed");"MessageLocale"="Culture";"PolicyTag"="[string]"; "Received"="[DateTime]";"Sent"="[DateTime]";"Size"="Size";"Subject"="[string]"}
There then follow three cases to check for;
1) An expression with bracketed sub-expressions: (body -like “test”) -and (attachment -like “*.csv”)
For this one the script reads each expression and the operator to join them. That operator should be a logical one (-and, -or etc.
if($ContentFilter -match "\((?<FirstPart>.+)\) \-(?<Operator>.+) \((?<SecondPart>.+)\)") { $LogicalOperatorShouldBeUsed=$True
2) A bracketed expression (that isn’t caught by the first test): (attachment -like “*.csv”)
For this just strip off the brackets and call the function again on what’s inside them.
elseIf ($ContentFilter -match "\((?<InsideBrackets>.+)\)") { Get-IsValidContentFilter -ContentFilter $Matches.InsideBrackets.Trim()
3) A standard content filter expression: attachment -like “*.csv”
elseIf ($ContentFilter -Match "(?<FirstPart>.+) \-(?<Operator>.+) (?<SecondPart>.+)") { $LogicalOperatorShouldBeUsed=$False
The two operands (“Attachment” and “*.csv” in the example above) and the operator (“like”) are saved. In this case the operator shouldn’t be a logical one.
After that any spaces are stripped off the three parts to be tested;
$FirstOperand=$Matches.FirstPart.Trim() $Operator=$Matches.Operator.Trim() $SecondOperand=$Matches.SecondPart.Trim()
If a logical comparison is being used the script recursively tests each part and then checks the operator is valid;
if ($LogicalOperatorShouldBeUsed) { $FirstPartIsValid=Get-IsValidContentFilter -ContentFilter $FirstOperand $SecondPartIsValid=Get-IsValidContentFilter -ContentFilter $SecondOperand $OperatorIsValid=$ValidContentFilterLogicalBinaryOperators -contains $Operator }else
If it’s a standard expression then the script checks the operator first and then ensures that the first operand is one of the ones listed in $ValidContentFilterFirstOperands.Keys
if (($ValidContentFilterFirstOperands.Keys -contains $FirstOperand)) { Write-Verbose "Matched First Operand Directly" $FirstPartIsValid=$True Write-Verbose "Testing 2nd Operand Type" $SecondPartIsValid=$False
The script also gets what kind of operand should be second (also from $ValidContentFilterFirstOperands). Then is performs a different check depending on whether it should be one of an array of values, a specific PowerShell type or a Culture setting.
$TestingType=$ValidContentFilterFirstOperands.$FirstOperand Write-Verbose "Checking $FirstOperand" Write-Verbose "Corresponding Type: $TestingType" if ($TestingType -is [Array]) { Write-Verbose "Found Type Array: $TestingType" $SecondPartIsValid=[bool]([string[]]$TestingType -contains $SecondOperand) }else { switch -regex ($TestingType) { "\[(?.+)\]" { Write-Verbose "Found Type $TestingType" $SecondPartIsValid=[string]$SecondOperand -as $Matches.TypeName Continue } "Culture" { Write-Verbose "Found Culture Reference" $SecondPartIsValid=([globalization.cultureinfo]::GetCultures("allCultures") | Where-Object {$_}) -contains $SecondOperand Continue } } } }
The last part defaults all three tests to false if none of the previous criteria were matched and then returns if all three parts of the expression were confirmed correct.
else { $FirstPartIsValid=$OperatorIsValid=$SecondPartIsValid=$False } } Write-Verbose "FirstOperand Valid: $FirstPartIsValid" Write-Verbose "Operator Valid: $OperatorIsValid" Write-Verbose "SecondOperand Valid: $SecondPartIsValid" $FirstPartIsValid -and $OperatorIsValid -and $SecondPartIsValid
Categories: Powershell
Leave a Reply