Permanent WMI Event consumer doesnt get triggered, temporary does - windows

I am trying to create a permanent WMI Event subscription to run a script when a USB Drive gets connected.
Here is how I create the Filter, Consumer and the binding:
$computer = "xxx"
$filterNS = "root\cimv2"
$wmiNS = "root\subscription"
$query = "Select * from __InstanceCreationEvent within 5 where targetinstance isa 'win32_logicaldisk'"
$filterName = "TestFilter"
$filterPath = Set-WmiInstance -Class __EventFilter `
-ComputerName $computer -Namespace $wmiNS -Arguments `
#{name=$filterName; EventNameSpace=$filterNS; QueryLanguage="WQL";
Query=$query}
$consumerPath = Set-WmiInstance -Class CommandLineEventConsumer `
-ComputerName $computer -Namespace $wmiNS `
-Arguments #{
name="TestConsumer";
ExecutablePath= "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe";
CommandLineTemplate = "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe -executionpolicy bypass -file D:\\reassignDriveletter.ps1"
}
Set-WmiInstance -Class __FilterToConsumerBinding -ComputerName $computer `
-Namespace $wmiNS -arguments #{Filter=$filterPath; Consumer=$consumerPath} |
out-null
Everything gets created without errors, i can see the Filter, consumer and binding in WMI event viewer but if i attach a USB drive nothing happens (first line of the script writes a log entry, thats how i know).
For testing purposes i created a normal event subscription like this:
$job = Register-WmiEvent -Query "Select * from __InstanceCreationEvent within 5 where targetinstance isa 'win32_logicaldisk'" -SourceIdentifier usb -Timeout 1000 -Action $scriptblock
which works absolutely fine.
Am I missing something obvious? I´d be grateful for any help.
Regards
Update: Just tested on a non-domain joined Win7 Computer and the same code works fine. (My workstation is Win8.1 domain-joined for the record). I will test on the target system and report back.

OK, after a couple of days of trial and error, I ended up adapting/using a different approach found here.
This will launch a script stored in D:\scripts everytime a USB is connected and it's permanent.
$filter = ([wmiclass]"\\.\root\subscription:__EventFilter").CreateInstance()
$filter.QueryLanguage = "WQL"
$filter.Query = "Select * from __InstanceCreationEvent within 5 where targetinstance isa 'win32_logicaldisk'"
$filter.Name = "USBFilter"
$filter.EventNamespace = 'root\cimv2'
$result = $filter.Put()
$filterPath = $result.Path
$consumer = ([wmiclass]"\\.\root\subscription:CommandLineEventConsumer").CreateInstance()
$consumer.Name = 'USBConsumer'
$consumer.CommandLineTemplate = "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe –ExecutionPolicy Bypass -file D:\scripts\reassignDriveletter.ps1"
$consumer.ExecutablePath = "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe"
$consumer.WorkingDirectory = "D:\scripts"
$result = $consumer.Put()
$consumerPath = $result.Path
$bind = ([wmiclass]"\\.\root\subscription:__FilterToConsumerBinding").CreateInstance()
$bind.Filter = $filterPath
$bind.Consumer = $consumerPath
$result = $bind.Put()
$bindPath = $result.Path
To delete these Perma Events, do this:
([wmi]$filterPath).Delete()
([wmi]$consumerPath).Delete()
([wmi]$bindPath).Delete()
My test script created a folder every time a USB drive was plugged in, so I could test it and it worked.
I'm running Windows 8.1 btw.

Related

Can WMI monitoring the event of formatting volume?

I want to implement a function is about monitoring format volume.
Is there any way to monitor the event of volume be formatted by using WMI or without using WMI?
I can use Register-WmiEvent to register a Wmi Event monitor to detect the plug/unplug of device (Below is sample code).
#Query for finding all device arrival events
$query = "SELECT * FROM Win32_VolumeChangeEvent WHERE EventType=2"
#Register an event subscription
Register-WmiEvent -SourceIdentifier "Qsirch-Monitor-Volume-Add" -Query $query -MessageData $yao -Action {
$volumeName = (Get-WMIObject -Class Win32_LogicalDisk -Filter "DeviceID='$($Event.SourceEventArgs.NewEvent.DriveName)'").VolumeName
Write-Host "$($Event.SourceEventArgs.NewEvent.DriveName) ($($volumeName)) was added"
} | Out-Null
When user create a new volume, this will print D: (New Volume) was added.
So can I use Wmi Event to achieve my goal?
Print D: was formatting when volume D was formatting.

How do I get notification to my phone, when my PC starts?

I want to know how can I get notification to my phone when my PC goes online (Turns On).
TeamViewer can help you: https://www.teamviewer.com.
If you install this app on your pc and you sync your user on your iPhone, I guess that whenever your pc starts you will get a notification
If your pc have login in, you can create an powershell script for any time when some one login in your pc you receive an email. Or, this is hard, you can creat an powershell script for when an service start on your pc, send you an email too, you set the service on the start.
$filter="*[System[EventID=7036] and EventData[Data='SIOS DataKeeper']]"
$A = Get-WinEvent -LogName System -MaxEvents 1 -FilterXPath $filter
$Message = $A.Message
$EventID = $A.Id
$MachineName = $A.MachineName
$Source = $A.ProviderName
$EmailFrom = "sios#medfordband.com"
$EmailTo = "sios#medfordband.com"
$Subject ="Alert From $MachineName"
$Body = "EventID: $EventID`nSource: $Source`nMachineName: $MachineName `n$Message"
$SMTPServer = "smtp.gmail.com"
$SMTPClient = New-Object Net.Mail.SmtpClient($SmtpServer, 587)
$SMTPClient.EnableSsl = $true
$SMTPClient.Credentials = New-Object System.Net.NetworkCredential("sios#medfordband.com", "MySMTPP#55w0rd");
$SMTPClient.Send($EmailFrom, $EmailTo, $Subject, $Body)
Like this, the event is the SIOS DataKeeper, when someone start the service you will receive an email. You can set the service for windows start, when the windows start you will receive an email.

Simple ForEach Combined with If Statment brings wrong results

Im fairly new to PS, im trying to perform a simple task, get list of computers by using the get-content, than though a foreach loop perform a wmi query to each device in the list and get the OS type, than with a IF statment check perform a different task depends on the OS, everntually win vista 7 8 10 needed to be seperated from xp.
I wrote the following PS script :
$computers=Get-Content C:\ComputerList\Computers.txt
$OSType=Get-WmiObject -Class Win32_operatingsystem -namespace "root\CIMV2" -ComputerName $computers
ForEach ( $compdevice in $computers ) {
if ( $OSType.buildnumber -eq "2600*" ) {
Write-Host $compdevice"'s OS type is XP" }
Else {
Write-Host $compdevice"'s Os type is Newer than xp"
}
}
in this case i get the same result for all computers ( im running the secret againt 2 win xp 1 win 7 and 1 win 8 in a domain envierment.
I've tried a different variation also :
$computers=Get-Content C:\ComputerList\Computers.txt
$OSType=Get-WmiObject -Class Win32_operatingsystem -namespace "root\CIMV2" -ComputerName $computers
ForEach ( $compdevice in $computers ) {
if ( $OSType.buildnumber -eq "2600*" ) {
Write-Host $compdevice"'s OS type is XP" }
Else {
Write-Host $compdevice"'s Os type is Newer than xp"
}
}
in both cases i get the exact same results ( all goes to one option of the IF statement )
I wonder, what am I doing wrong ?
Note - I was trying to filter by caption, buildnumber and version. and even wild card in the IF statement, it doesn't work well
A couple of things are wrong.
First, you have captured wmi content into an array but you do not have a relation of that data to computername. If you include wmi lookup inside the foreach loop, then you have the relationship established. In other words:
$computers=Get-Content C:\ComputerList\Computers.txt
ForEach ( $compdevice in $computers ) {
$OSType=Get-WmiObject -Class Win32_operatingsystem -namespace "root\CIMV2" -ComputerName $compdevice
### you are using '-eq' so you should provide the actual number, you would use * with -like operator
if ( $OSType.buildnumber -eq "2600" ) {
...

Alternative to Win32_Product?

After playing around with querying Win32_Product to find a software version, I couldn't understand why the results were so dog-slow. As much as 15 times slower than querying Win32_service or Win32_process. So coming here to see if I'm missing something, I find that others have reported the same issue, and this article explains why.
The most-often suggested alternative to finding installed software is querying a registry entry or three. That was going to be my first solution, except my company hasn't moved to configure servers to accept PSRemoting yet. Any reg queries just return Kerberos authentication errors. I can enable PSRemoting on individual servers, but my team supports 30K systems. So that solution is out.
Bottom line, we're upgrading Symantec Endpoint Protection from v. 11 to v. 12, and I want a simple check to find what version is installed on a server. Are there any alternatives to find the version other than Win32_Product and registry queries?
I use the registry remotely, without PSRemoting. Here's the function I wrote and use daily to query software.
Function Get-RemoteSoftware{
<#
.SYNOPSIS
Displays all software listed in the registry on a given computer.
.DESCRIPTION
Uses the SOFTWARE registry keys (both 32 and 64bit) to list the name, version, vendor, and uninstall string for each software entry on a given computer.
.EXAMPLE
C:\PS> Get-RemoteSoftware -ComputerName SERVER1
This shows the software installed on SERVER1.
#>
param (
[Parameter(mandatory=$true,ValueFromPipelineByPropertyName=$true)][string[]]
# Specifies the computer name to connect to
$ComputerName
)
Process {
foreach ($Computer in $ComputerName)
{
#Open Remote Base
$reg=[microsoft.win32.registrykey]::OpenRemoteBaseKey('LocalMachine',$Computer)
#Check if it's got 64bit regkeys
$keyRootSoftware = $reg.OpenSubKey("SOFTWARE")
[bool]$is64 = ($keyRootSoftware.GetSubKeyNames() | ? {$_ -eq 'WOW6432Node'} | Measure-Object).Count
$keyRootSoftware.Close()
#Get all of they keys into a list
$softwareKeys = #()
if ($is64){
$pathUninstall64 = "SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall"
$keyUninstall64 = $reg.OpenSubKey($pathUninstall64)
$keyUninstall64.GetSubKeyNames() | % {
$softwareKeys += $pathUninstall64 + "\\" + $_
}
$keyUninstall64.Close()
}
$pathUninstall32 = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall"
$keyUninstall32 = $reg.OpenSubKey($pathUninstall32)
$keyUninstall32.GetSubKeyNames() | % {
$softwareKeys += $pathUninstall32 + "\\" + $_
}
$keyUninstall32.Close()
#Get information from all the keys
$softwareKeys | % {
$subkey=$reg.OpenSubKey($_)
if ($subkey.GetValue("DisplayName")){
$installDate = $null
if ($subkey.GetValue("InstallDate") -match "/"){
$installDate = Get-Date $subkey.GetValue("InstallDate")
}
elseif ($subkey.GetValue("InstallDate").length -eq 8){
$installDate = Get-Date $subkey.GetValue("InstallDate").Insert(6,".").Insert(4,".")
}
New-Object PSObject -Property #{
ComputerName = $Computer
Name = $subkey.GetValue("DisplayName")
Version = $subKey.GetValue("DisplayVersion")
Vendor = $subkey.GetValue("Publisher")
UninstallString = $subkey.GetValue("UninstallString")
InstallDate = $installDate
}
}
$subkey.Close()
}
$reg.Close()
}
}
}
That Get-RemoteSoftware works great--assuming remote registry service is started on the remote system. If not you will get an error. I always check if this is started and start it if not, then stop it when done. I wonder if this is why the otherwise-great function received down-votes.
Here is a slightly modified version that will check and start the remote registry service, and stop when finished.
Function Get-WmiCustom2([string]$computername,[string]$namespace,[string]$class,[int]$timeout=15,[string]$whereclause='')
{
#Function Get-WMICustom2 by MSFT's Daniele Muscetta
#This is a modified version to add where clause parameter, optional
#Original function: http://blogs.msdn.com/b/dmuscett/archive/2009/05/27/get_2d00_wmicustom.aspx
$ConnectionOptions = new-object System.Management.ConnectionOptions
$EnumerationOptions = new-object System.Management.EnumerationOptions
$timeoutseconds = new-timespan -seconds $timeout
$EnumerationOptions.set_timeout($timeoutseconds)
$assembledpath = "\\" + $computername + "\" + $namespace
$Scope = new-object System.Management.ManagementScope $assembledpath, $ConnectionOptions
try {
$Scope.Connect()
} catch {
$result="Error Connecting " + $_
return $Result
}
$querystring = "SELECT * FROM " + $class + " " + $whereclause
$query = new-object System.Management.ObjectQuery $querystring
$searcher = new-object System.Management.ManagementObjectSearcher
$searcher.set_options($EnumerationOptions)
$searcher.Query = $querystring
$searcher.Scope = $Scope
trap { $_ } $result = $searcher.get()
return $result
}
Function Get-RemoteSoftware{
<#
.SYNOPSIS
Displays all software listed in the registry on a given computer.
.DESCRIPTION
Uses the SOFTWARE registry keys (both 32 and 64bit) to list the name, version, vendor, and uninstall string for each software entry on a given computer.
.EXAMPLE
C:\PS> Get-RemoteSoftware -ComputerName SERVER1
This shows the software installed on SERVER1.
#>
param (
[Parameter(mandatory=$true,ValueFromPipelineByPropertyName=$true)][string[]]
# Specifies the computer name to connect to
$ComputerName
)
Process {
foreach ($Computer in $ComputerName)
{
$ChangeStateBack=$False
$RemoteRegistryObj=""
$ServiceWMIObj=#(get-wmicustom2 -class "win32_service" -namespace "root\cimv2" -whereclause "WHERE name='RemoteRegistry'" -computername $computername –timeout 60 -erroraction stop)
if ($ServiceWMIObj.Count -gt 0) {
$RemoteRegistryObj = $ServiceWMIObj[0]
if ($RemoteRegistryObj.State -ne 'Running') {
$ChangeStateBack=$true
$RemoteRegistryObj.InvokeMethod("StartService",$null) | Out-Null
Start-Sleep -m 1800
#give it a chance to actually start. 1.5 second delay
}
}
#Open Remote Base
$reg=[microsoft.win32.registrykey]::OpenRemoteBaseKey('LocalMachine',$Computer)
#Check if it's got 64bit regkeys
$keyRootSoftware = $reg.OpenSubKey("SOFTWARE")
[bool]$is64 = ($keyRootSoftware.GetSubKeyNames() | ? {$_ -eq 'WOW6432Node'} | Measure-Object).Count
$keyRootSoftware.Close()
#Get all of they keys into a list
$softwareKeys = #()
if ($is64){
$pathUninstall64 = "SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall"
$keyUninstall64 = $reg.OpenSubKey($pathUninstall64)
$keyUninstall64.GetSubKeyNames() | % {
$softwareKeys += $pathUninstall64 + "\\" + $_
}
$keyUninstall64.Close()
}
$pathUninstall32 = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall"
$keyUninstall32 = $reg.OpenSubKey($pathUninstall32)
$keyUninstall32.GetSubKeyNames() | % {
$softwareKeys += $pathUninstall32 + "\\" + $_
}
$keyUninstall32.Close()
#Get information from all the keys
$softwareKeys | % {
$subkey=$reg.OpenSubKey($_)
if ($subkey.GetValue("DisplayName")){
$installDate = $null
if ($subkey.GetValue("InstallDate") -match "/"){
$installDate = Get-Date $subkey.GetValue("InstallDate")
}
elseif ($subkey.GetValue("InstallDate").length -eq 8){
$installDate = Get-Date $subkey.GetValue("InstallDate").Insert(6,".").Insert(4,".")
}
New-Object PSObject -Property #{
ComputerName = $Computer
Name = $subkey.GetValue("DisplayName")
Version = $subKey.GetValue("DisplayVersion")
Vendor = $subkey.GetValue("Publisher")
UninstallString = $subkey.GetValue("UninstallString")
InstallDate = $installDate
}
}
$subkey.Close()
}
$reg.Close()
if ($ChangeStateBack){
$RemoteRegistryObj.InvokeMethod("StopService",$null) | Out-Null
}
}
}
}
This is using a custom WMI get wrapper that someone at MSFT wrote, so if this snippet is copied in its entirety it will work as-is. You could modify it back to the standard get-wmiobject function, but there is no timeout built in to that. In some [not all that rare] situations the remote WMI responder will hang indefinitely with the default WMI so this adds a timeout.
-Dane
There's actually a follow-up Hey! Scripting Guys article
Use PowerShell to Find Installed Software that discusses other more efficient ways to grab that information. Briefly, use one of two commands:
Get-ItemProperty HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\* |
Select-Object DisplayName, DisplayVersion, Publisher, InstallDate |
Format-Table –AutoSize
Get-ItemProperty HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\* |
Select-Object DisplayName, DisplayVersion, Publisher, InstallDate |
Format-Table –AutoSize
I would recommend reading this scripting guy article for why Win32_Product is bad along with alternatives, but to quote the relevant section:
...As it turns out, the action of querying Win32_Product has the
potential to cause some havoc on your systems. Here is the essence of
KB974524.
The Win32_product class is not query optimized. Queries such as
“select * from Win32_Product where (name like ‘Sniffer%’)” require WMI
to use the MSI provider to enumerate all of the installed products and
then parse the full list sequentially to handle the “where” clause:,
This process initiates a consistency check of packages installed, and
then verifying and repairing the installations. If you have an
application that makes use of the Win32_Product class, you should
contact the vendor to get an updated version that does not use this
class.
On Windows Server 2003, Windows Vista, and newer operating
systems, querying Win32_Product will trigger Windows Installer to
perform a consistency check to verify the health of the application.
This consistency check could cause a repair installation to occur. You
can confirm this by checking the Windows Application Event log. You
will see the following events each time the class is queried and for
each product installed:
Event ID: 1035
Description: Windows Installer reconfigured the product. Product Name: <ProductName>.
Product Version: <VersionNumber>. Product Language: <languageID>.
Reconfiguration success or error status: 0.
I usually use the Win32Reg_AddRemovePrograms as we are using SCCM which installs this class. If you are not using SCCM stick with a registry query like the one #Chris N posted.
PS:\>Measure-Command {gwmi win32reg_addremoveprograms}
Days : 0
Hours : 0
Minutes : 0
Seconds : 0
Milliseconds : 162
Ticks : 1623758
TotalDays : 1.87934953703704E-06
TotalHours : 4.51043888888889E-05
TotalMinutes : 0.00270626333333333
TotalSeconds : 0.1623758
TotalMilliseconds : 162.3758

Query AD to find all the Computer in an OU with TC in their Name

I'm trying to search AD for all machines in a given OU that have 'TC' in their name, this is what I have so far, but its returning all machines, I need it to return just the machines with 'TC' in their name.
$root = ([adsi]'LDAP://OU=PCs,OU=Student Computers,DC=student,DC=belfastmet,DC=int','objectCategory=computer')
$searcher = new-object System.DirectoryServices.DirectorySearcher($root)
$searcher.filter = "(objectCategory=computer)"
$searcher.pageSize=1000
$searcher.propertiesToLoad.Add("name")
$computers = $searcher.findall()
$computers | foreach {$_.properties.name}
Not really sure what I should be doing from this point, I am a Powershell Newbie.
You have two options. You can get all computers and then filter using Powershell cmdlets, or your ldap filter reflects what you want (better). Try this:
$searcher.filter = "(&(objectCategory=computer)(cn=TN*))"
With ActiveDirectoryModule, you could find machines in a specific OU using filter and limit the search to the OU (assuming YourDomain.com\YourOU in the example below) you want with SearchBase:
$adcomputers = Get-ADComputer -Filter {name -like "TC*"} -Searchbase "OU=YourOU,DC=YourDomain,DC=com"
If you have the AD module available to you, you can do this with a single cmdlet.
get-adcomputer -filter {name -like "*TC*"}

Resources