Functions/New-WmiEventFilter.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
258
259
# TODO: Check if a filter with the same name already exists, and prompt user if they want to overwrite (use the -Force parameter)
# TODO (DONE): Check if user is an administrator. If not, write an error message informing them of this, and hope they haven't set $ErrorActionPreference to 'SilentlyContinue' :)

#region New-WmiEventFilter
function New-WmiEventFilter
{
    <#
        .Synopsis
        Creates a new WMI event filter.
         
        .Description
        Creates a new instance of __EventFilter in the specified namespace. This is typically used in concert with an instance of an event consumer, and a __FilterToConsumerBinding.
         
        .Parameter Name
        A unique name for the event filter. If this parameter is not specified, then a random GUID will be used to identify the event filter. It is highly recommended to use a friendly name for event filters, to avoid confusion when managing them in the future.
         
        .Parameter Namespace
        The WMI namespace in which the the event filter will be executed. This is NOT the same as the namespace where the __EventFilter instance will be created.
         
        .Parameter Query
        The WQL event query that the filter will use to poll for events.
         
        .Parameter QueryLanguage
        The language used to write the event query. This will almost always be 'WQL'.
         
        .Parameter ComputerName
        The computer on which to create the WMI event filter.
         
        .Inputs
        None
         
        .Outputs
        A System.Management.ManagementObject, representing an instance of the __EventFilter WMI class. The output can be stored in a variable, and then used with New-WmiFilterToConsumerBinding.
         
        .Component
        Windows Management Instrumentation (WMI)
         
        .Link
        http://trevorsullivan.net
         
        .Link
        http://powershell.artofshell.com
         
        .Link
        http://msdn.microsoft.com/en-us/library/aa392902(VS.85).aspx
 
        .Link
        http://msdn.microsoft.com/en-us/library/aa394639(VS.85).aspx
         
        .Link
        http://www.codeproject.com/KB/system/PermEvtSubscriptionMOF.aspx?display=Print
    #>


    [CmdletBinding(
          SupportsShouldProcess = $false
        , SupportsTransactions  = $false  # Script cmdlets cannot support transactions. Only compiled cmdlets can support them.
        , ConfirmImpact         = 'Low'   # This function has a minimal impact on data loss
    )]

    #region New-WmiEventFilter Parameters
    param(
        [Parameter(
              Mandatory   = $false
            , Position    = 1
            , HelpMessage = "Please enter a name for the event filter."
        )]
        [string]
        [alias("FilterName", "fltr")]
        ${Name}
        ,
        # TODO: Use script validation to ensure that this namespace is valid
        [Parameter(
              Mandatory   = $false
            ,  Position    = 0
            , HelpMessage = "Please specify the namespace where the event query should be executed against."
        )]
        ${EventNamespace} = 'root\cimv2'
        ,
        [Parameter(
              Mandatory   = $true
            , ParameterSetName = 'WqlQuery'
            , HelpMessage = "Please specify the WQL event query to be used for this event filter."
        )]
        [string]
        [alias("WQLQuery", "qry")]
        ${Query}
        ,
        # This parameter MUST be set to 'WQL'
        [parameter(
              Mandatory   = $false
            , HelpMessage = "Please specify the query language for this event filter; Must be 'WQL'."
        )]
        [ValidateSet("WQL")]
        [string]
        ${QueryLanguage} = 'WQL'
        ,
        [Parameter(
              Mandatory   = $false
            , HelpMessage = "Please specify the computer to create the WMI event filter on."
        )]
        [string]
        ${ComputerName} = '.'
        ,
        [Parameter(
              Mandatory   = $false
            , HelpMessage = "Please specify the namespace where the __EventFilter instance should be created."
        )]
        <#
        This [ValidateScript()] block ensures that the user has passed a valid namespace to the function.
        UPDATE: After testing, this won't work, because we can't use ${ComputerName} to test a namespace on a remote system.
                This validation code will have to go in the BEGIN { ... } block
        [ValidateScript({
            Write-Verbose -Message ${ComputerName}
            if (([wmiclass]"\\${ComputerName}\root\cimv2:__ThisNamespace").__namespace -eq $_)
            {
                return $true
            }
            else
            {
                return $false
            }
        })]
        #>

        [ValidateSet("root\subscription")]
        ${Namespace} = 'root\subscription'
        
        # TODO: Build a new parameter set ( call it "QueryBuilder"?) to allow for query building. Include parameters such as:
        # "ClassName" = ex. Win32_Process, Win32_ProcessStartTrace
        # If "ClassName" is implemented, use the [ValidateScript()] attribute to ensure the class exists
        # "EventType" = Intrinsic or Extrinsic -- alternative: write some code to automatically detect this based on the ClassName?
        # "PollingInterval" = the polling interval for the WITHIN clause of the event query
    )
    #endregion New-WmiEventFilter Parameters
    
    #region New-WmiEventFilter Begin block
    begin
    {
        ${CmdletName} = $Pscmdlet.MyInvocation.MyCommand.Name
        Write-Verbose -Message "${CmdletName}: Running the BEGIN block"

        #region Check if user token is an administrator
        ${Identity} = [System.Security.Principal.WindowsIdentity]::GetCurrent()
        ${Principal} = new-object System.Security.Principal.WindowsPrincipal(${Identity})
        ${IsAdmin} = $Principal.IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator)
        if (-not ${IsAdmin})
        {
            Write-Error -Message "${CmdletName}: User is not an administrator. Cannot continue." -RecommendedAction "${CmdletName}: Please run this function as an administrator."
        }
        #endregion Check if user token is an administrator
  
          #region Check for existence of ${EventNamespace} and ${Namespace}
        if (([wmiclass]"\\${ComputerName}\${EventNamespace}:__ThisNamespace").__namespace -eq ${EventNamespace})
        {
            Write-Verbose -Message "${CmdletName}: Validated the existence of WMI namespace passed to `${EventNamespace} (\\${ComputerName}\${EventNamespace})"
        }
        else
        {
            Write-Error -Message "${CmdletName}: Could not validate the existence of WMI namespace passed to `${EventNamespace} (\\${ComputerName}\${EventNamespace})" -TargetObject ${EventNamespace} -ErrorAction Stop
        }

        if (([wmiclass]"\\${ComputerName}\${Namespace}:__ThisNamespace").__namespace -eq ${Namespace})
        {
            Write-Verbose -Message "${CmdletName}: Validated the existence of WMI namespace passed to `${Namespace} (\\${ComputerName}\${Namespace})"
        }
        else
        {
            Write-Error -Message "${CmdletName}: Could not validate the existence of WMI namespace passed to `${Namespace} (\\${ComputerName}\${Namespace})" -TargetObject ${Namespace} -ErrorAction Stop
        }
          #endregion Check for existence of ${EventNamespace} and ${Namespace}
  
          #region Check for existing __EventFilter with same name
        # Check to see if an instance of __EventFilter with the requested name already exists
        if (${Name})
        {
            Write-Verbose -Message "Looking for existing instances of __EventFilter in namespace ${Namespace} with the name ${Name}"
            ${ExistingFilter} = Get-WmiObject -Namespace ${Namespace} -ComputerName ${ComputerName} -Class __EventFilter -Filter "Name = '${Name}'"
            if (${ExistingFilter})
            {
                Write-Warning -Message "${CmdletName}: __EventFilter instance already exists with name ${Name}"
                #Write-Output -InputObject ${ExistingFilter}
            }
        }
          #endregion Check for existing __EventFilter with same name
    }
    #endregion New-WmiEventFilter Begin block
    
    #region New-WmiEventFilter Process block
    process
    {
        ${NewFilter} = ([wmiclass]"\\${ComputerName}\${Namespace}:__EventFilter").CreateInstance()
        Write-Verbose -Message "${CmdletName}: Created new instance of __EventFilter"
        
        # The QueryLanguage will always be 'WQL' for WMI event queries
        ${NewFilter}.{QueryLanguage} = ${QueryLanguage}
        Write-Verbose -Message "${CmdletName}: Defined the QueryLanguage property"
        
        # The WQL event query that will be used to capture events
        if (${NewFilter})
        {
            ${NewFilter}.{Query} = ${Query}
            Write-Verbose -Message "${CmdletName}: Defined the Query (${Query}) property"
        }
        
        # The namespace that events will be captured in.
        # Example: root\cimv2, if your event query targets Win32_Process
        if (${EventNamespace})
        {
            ${NewFilter}.{EventNamespace} = ${EventNamespace}
            Write-Verbose -Message "${CmdletName}: Defined the EventNamespace (${EventNamespace}) property"
        }
        else
        {
            Write-Warning -Message "${CmdletName}: `${EventNamespace} parameter was not specified." -WarningAction "Please specify a valid WMI namespace"
        }
        
        # The unique name for the event filter instance
        if (${Name})
        {
            ${NewFilter}.{Name} = ${Name}
            Write-Verbose -Message "${CmdletName}: Defined the Name (${Name}) property"
        }
        else
        {
            Write-Verbose "${CmdletName}: `${Name} parameter was not specified. Defaulting to random GUID for Name property."
        }
        
        # Write the __EventFilter instance to the WMI provider
        Write-Verbose -Message "${CmdletName}: Preparing to commit __EventFilter instance to WMI: ${PutResult}"
        ${PutResult} = ${NewFilter}.Put()

        if (${PutResult}.Path)
        {
            Write-Verbose -Message ('${CmdletName}: Committed new __EventFilter instance: ' + ${PutResult}.{Path})
            # Write new __EventFilter instance to the pipeline
            Write-Output $([wmi]"$(${PutResult}.{Path})")
        }
        else
        {
            Write-Error -Message "${CmdletName}: Failed to commit __EventFilter instance to WMI." -ErrorAction Stop
        }

    }
    #endregion New-WmiEventFilter Process block
    
    #region New-WmiEventFilter End block
    end
    {
        Write-Verbose -Message "${CmdletName}: Running the END block"
    }
    #endregion New-WmiEventFilter End block
}
#endregion New-WmiEventFilter

# Export the advanced function for use in the module
Export-ModuleMember -Function New-WmiEventFilter

# Create and export an alias for the advanced function
Set-Alias -Name nwmif -Value New-WmiEventFilter
Export-ModuleMember -Alias nwmif