Register-DSCTrigger.ps1
function Register-DSCTrigger { <# .Synopsis Registers a trigger to run a Powershell configuration .Description Registers a WMI event trigger that, when the event occurs, will run a PowerShell configuration .Link Get-DSCTrigger .Link UnRegister-DSCTrigger #> [OutputType([Nullable])] param( # The name of the DSC trigger [Parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true, Position=0)] [string] $Name, # The event filter [Parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true, Position=1)] [string] $EventFilter, # The event namespace [Parameter(ValueFromPipelineByPropertyName=$true)] [string] $EventNamespace = 'root\cimv2', # The configuration that will be applied [Parameter(ValueFromPipelineByPropertyName=$true,Position=2)] [ScriptBlock] $Configuration, # If set, will delete existing registrations. [Parameter(ValueFromPipelineByPropertyName=$true)] [switch] $Force ) process { #region Double Check that the Configuration actually has a configuration $configTokens = [Management.Automation.PSParser]::Tokenize("$Configuration", [ref]$null) $configurationName = '' for($i =0; $i -lt $configTokens.Count;$i++) { if ($configTokens[$i].Type -eq 'keyword' -and $configTokens[$i].Content -eq 'configuration') { $configurationName = $configTokens[$i + 1].Content break } } if (-not $configurationName) { Write-Error "Configuration script must include a configuration" return } #endregion Double Check that the Configuration actually has a configuration # The script before the configuration is a boilerplate $fullScript = { $myCmd = $MyInvocation.MyCommand }.ToString() + $Configuration + { # Any parameters to the configuration can be stored in a .parameters.clixml file. $settingsFilePath = $myCmd.Path.Replace('.ps1','.parameters.clixml') $settings = @{} if ([IO.File]::Exists($settingsFilePath)) { $settingsFromFile = Import-Clixml $settingsFilePath if ($settingsFromFile -as [Hashtable]) { $settings += ($settingsFromFile -as [Hashtable]) } } } + @" `$configurationResult = $configurationName @Settings "@ +{ # If the configuration produced a result, then Start-DSCConfiguration if ($configurationResult) { Start-DscConfiguration -Path ($configurationResult | Split-Path) -Wait -Force -Verbose } # If there were any errors, export them to a .clixml. The file write time should be enough help in pairing up the problems. if ($Error.Count) { $Error | Export-Clixml ".\$([Runspace]::DefaultRunspace.InstanceId).errors.clixml" } } # Output the generated resources to programdata, so that the current user and the DSC user can access it $outDir = "$env:ProgramData\cFg\DSCTrigger\$Name\" # Make the directory if it doesn't exist if (-not (Test-Path $outDir)) { $null = New-Item -ItemType Directory -Path $outDir -Force } # Write out the .PS1 file [IO.File]::WriteAllText($outDir + "$configurationName.ps1", $fullScript) #region Find the Existing Consumer and Delete It (Or Bail) $existingConsumer = Get-WmiObject -Class CommandLineEventConsumer -Filter "Name = '${Name}_Consumer'" -Namespace root\subscription if ($existingConsumer -and -not $force) { Write-Error "Consumer ${Name}_Consumer already exists, use -Force to overwrite" return } elseif ($existingConsumer -and $force) { $existingConsumer.Delete() } #endregion Find the Existing Consumer and Delete It (Or Bail) #region Register the Permanent Event Consumer $consumerPath = Set-WmiInstance -Class CommandLineEventConsumer -Arguments @{ Name = "${Name}_DSCConsumer" ExecutablePath = "$psHome\powershell.exe" CommandLineTemplate = "$psHome\powershell.exe -executionpolicy bypass -File $env:ProgramData\cFg\DSCTrigger\$Name\$ConfigurationName.ps1" WorkingDirectory = "$env:ProgramData\cFg\DSCTrigger\$Name\" } -Namespace root\Subscription #endregion Register the Permanent Event Consumer #region Find the Existing Filter and Delete It (Or Bail) $existingFilter = Get-WmiObject -Class __EventFilter -Filter "Name = '${Name}_DSCFilter'" -Namespace root\subscription if ($existingFilter -and -not $force) { Write-Error "Filter ${Name}_Filter already exists, use -Force to overwrite" return } elseif ($existingFilter -and $force) { $existingFilter.Delete() } #endregion Find the Existing Filter and Delete It (Or Bail) #region Register the Event Filter $TheEventFilterPath = Set-WmiInstance -Class __EventFilter -Arguments @{ EventNamespace = "$EventNamespace" Query = $EventFilter Name = "${Name}_Filter" QueryLanguage = 'WQL' } -Namespace root\subscription #endregion Register the Event Filter #region Find the Existing Binding and Delete It (Or Bail) $ExistingBinding = Get-WmiObject -Class __FilterToConsumerBinding -Namespace root\subscription | ? { $_.Filter -eq $TheEventFilterPath -and $_.Consumer -eq $consumerPath } if ($existingBinding -and -not $force) { Write-Error "Binding ${Name} already exists, use -Force to overwrite" return } elseif ($existingBinding -and $force) { $existingBinding.Delete() } #endregion Find the Existing Binding and Delete It (Or Bail) #region Register the Binding $theBinding = Set-WmiInstance -Class __FilterToConsumerBinding -Arguments @{ Filter = $TheEventFilterPath Consumer = $consumerPath } -Namespace root\subscription if ($VerbosePreference -ne 'silentlycontinue') { Write-Verbose ($theBinding | Out-String) } #endregion Register the Binding } } |