Private/Update-AtwsFilter.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
<#
 
    .COPYRIGHT
    Copyright (c) ECIT Solutions AS. All rights reserved. Licensed under the MIT license.
    See https://github.com/ecitsolutions/Autotask/blob/master/LICENSE.md for license information.
 
#>


Function Update-AtwsFilter {
    <#
      .SYNOPSIS
      This function parses an Atws filterstring or -array and makes sure it conforms to the format needed by
      core functions.
      .DESCRIPTION
      All Get- are used to make it possible to generate a correctly formatted QueryXML that does what the user
      expects. To make this easier we use an approximation of the default operators in PowerShell, so that it is
      as easy as possible for new users that are experienced in PowerShell, but not in QueryXML to use the module.
      To make variable expansion possible the function should be dot-sourced from the entity wrapper functions.
      .INPUTS
      [string[]]
      .OUTPUTS
      [string[]]
      .EXAMPLE
      Update-AtwsFilter -Filterstring <string[]>
      Parses an Atws filterstring or -array and makes sure it conforms to the format needed by core functions.
      .NOTES
      NAME: Update-AtwsFilter
       
  #>

    [cmdletbinding()]
    Param
    (
        [Parameter(
            Mandatory = $true,
            ValueFromPipeline = $true
        )]
        [string[]]
        $Filterstring
    )

    begin {
        # Enable modern -Debug behavior
        if ($PSCmdlet.MyInvocation.BoundParameters['Debug'].IsPresent) { $DebugPreference = 'Continue' }
    
        Write-Verbose ('{0}: Begin of function' -F $MyInvocation.MyCommand.Name)
    }

    process {
        # $Filter is usually passed as a flat string. Convert it to an array.
        if ($Filterstring.Count -eq 1 -and $Filterstring -match ' ' ) { 
            # First, make sure it is a single string and replace parenthesis with our special operator
            $Filterstring = $Filterstring -join ' ' -replace '\(', ' -begin ' -replace '\)', ' -end '
    
            # Removing double possible spaces we may have introduced
            Do { $Filterstring = $Filterstring -replace ' ', ' ' }
            While ($Filterstring -match ' ')

            # Split back in to array, respecting quotes
            $Words = $Filterstring.Trim().Split(' ')
            [string[]]$Filterstring = @()
            $Temp = @()
            foreach ($Word in $Words) {
                if ($Word -match '^[\"\'']' -and $Word -match "[\'\""]$") {
                    $Filterstring += $Word.Trim('"''')
                }
                elseif ($Temp.Count -eq 0 -and $Word -match '^[\"\'']') {
                    $Temp += $Word.TrimStart('"''')
                }
                elseif ($Temp.Count -gt 0 -and $Word -match "[\'\""]$") {
                    $Temp += $Word.TrimEnd("'""")
                    $Filterstring += $Temp -join ' '
                    $Temp = @()
                }
                elseif ($Temp.Count -gt 0) {
                    $Temp += $Word
                }
                else {
                    $Filterstring += $Word
                }
            }
        }
      
        Write-Debug ('{0}: Checking query for variables that have survived as string' -F $MyInvocation.MyCommand.Name)
      
        $NewFilter = @()
        foreach ($Word in $Filterstring) {
            $value = $Word
            # Is it a variable name?
            if ($Word -match '^\$\{?(\w+:)?(\w+)\}?(\.\w[\.\w]+)?$') {
                # If present, first group is SCOPE. In the context of this function, the only possible scope
                # is Global; Script = the module, local is internal to this function.
                $Scope = 'Global' # or numbered scope 2
        
                # The variable name MUST be present
                $VariableName = $Matches[2]

                # A property tail CAN be present
                $PropertyTail = $Matches[3]
        
                # Check that the variable exists
                $Variable = try
                { Get-Variable -Name $VariableName -Scope $Scope -ValueOnly -ErrorAction Stop }
                catch {
                    Write-Error ('{0}: Could not find any variable called ${1}. Is it misspelled or has it not been set yet?' -f $MyInvocation.MyCommand.Name, $VariableName)
                    # Force stop of calling script, because this will completely break the query
                    Return
                }

                # Test if the variable "Variable" has been set
                if (Test-Path Variable:Variable) {
                    Write-Debug ('{0}: Substituting {1} for its value' -F $MyInvocation.MyCommand.Name, $Word)
                    if ($PropertyTail) {
                        # Add properties back
                        $Expression = '$Variable{0}' -F $PropertyTail
  
                        # Invoke-Expression is considered risky from an SQL injection kind of perspective. But by only
                        # permitting a .dot separated string of [a-zA-Z0-9_] we are PROBABLY safe...
                        $value = Invoke-Expression -Command $Expression
                    }
                    else {
                        # $value must be removed or it will retain the type of the first value
                        Remove-Variable -Name value -Force
                        $value = $Variable
                    }
                    if ($null -eq $value) {
                        Write-Error ('{0}: Could not find any variable called {1}. Is it misspelled or has it not been set yet?' -F $MyInvocation.MyCommand.Name, $Expression)
                        # Force stop of calling script, because this will completely break the query
                        Return
                    }
                    else { 
                        # Normalize dates. Important to avoid QueryXML problems
                        if ($value.GetType().Name -eq 'DateTime') {
                            [string]$value = ConvertTo-AtwsDate -DateTime $value
                        }
                    }
                }
            }
            $NewFilter += $value
        }
 
    }

    end {
        Return $NewFilter
    }
}