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" ) {
...
Related
Hello to the community;
I am in a little problem, I am not an expert in powershell but I need it to be able to perform some automated actions.
I need to validate that shortcuts point to a specific path example C:\Program Files\Internet Explorer\iexplorer.exe
When it locates a shortcut that matches the search, it generates an action.
I am applying this small code, I cannot make a correct comparison of the values
$WSShell = New-Object -ComObject Wscript.Shell
$shortcutfiles = dir C:\Users\*\documents\*.lnk
$IE = "C:\Program Files\Internet Explorer\iexplorer.exe"
foreach ($shortcutfile in $shortcutfiles ) {
if ($WSShell.CreateShortcut($shortcutfile).targetPath -eq $IE)
{
Write-Host "$IE"
}
}
Freddy,
The following code works on my system using PS 5.1 & Core 7.2.6.
Note: I changed the parameters to meet the .lnk files location on my machine. I didn't have any IE shortcuts to use.
Clear-Host
$ST = "C:\Windows\System32\schtasks.exe"
$shortcutfiles = Get-ChildItem -Path "G:\*\BTB" -Filter "*.lnk" -recurse
foreach ($shortcutfile in $shortcutfiles ) {
$CCFile = $($shortcutfile.FullName)
$WSShell = New-Object -ComObject 'Wscript.Shell'
if ((($WSShell.CreateShortcut("$CCFile")).targetPath) -contains "$ST") {
"$CCFile" #For Debugging!
"$ST"
}
$WSShell = $Null #Clear COM Object
}
Note: I made sure everything was resolved via use of parenthesis in the IF statement. Also note the use of -contains vs -eq since shortcuts often place parameters on the target line as they do with my Scheduled Task shortcuts.
Sample Output:
G:\BEKDocs\BTB\2. Maintanence\7-Command Prompt - System Level.lnk
C:\Windows\System32\schtasks.exe
G:\BEKDocs\BTB\2. Maintanence\Enable Network SafeBoot.lnk
C:\Windows\System32\schtasks.exe
G:\BEKDocs\BTB\2. Maintanence\Enable SafeBoot.lnk
C:\Windows\System32\schtasks.exe
G:\BEKDocs\BTB\Office Tools\Office Ribbon Editor.lnk
C:\Windows\System32\schtasks.exe
G:\BEKDocs\BTB\Command Prompt as Admin.lnk
C:\Windows\System32\schtasks.exe
G:\BEKDocs\BTB\Everything Search.lnk
C:\Windows\System32\schtasks.exe
G:\BEKDocs\BTB\Regedit as Admin.lnk
C:\Windows\System32\schtasks.exe
G:\BEKDocs\BTB\Search Content.lnk
C:\Windows\System32\schtasks.exe
G:\BEKDocs\BTB\Shutdown ALL.lnk
C:\Windows\System32\schtasks.exe
I am writing a Powershell script which will be integrated into a product designed for 32 bit Windows machines. So on invocation it will by default run on the x86 Powershell even on 64 bit machines.
I tried using [System.IntPtr]::Size but the output differs with the Powershell version on the same machine.
Powershell(32 bit) -
PS D:\powershellScripts> [System.IntPtr]::Size
4
Powershell(64 bit) on same machine-
PS D:\powershellScripts> [System.IntPtr]::Size
8
I need an independent solution which helps me distinguish the address size of the underlying machine.
Thanks to BACON's link to a closely related question with this answer, the following concise solution is possible, which works from both 32-bit and 64-bit PowerShell sessions:
$pointerSizeInBytes = (4, 8)[[Environment]::Is64BitOperatingSystem]
A [bool] value interpreted as an array index ([int]) maps to either 0 ($false) or 1 ($true), which is used here to select the appropriate value from array 4, 8.
Here's the original form of the answer, which may have some related information of interest:
A simple test, assuming that you're always running from a 32-bit PowerShell instance:
$is64Bit = Test-Path C:\Windows\SysNative
32-bit processes (only) on 64-bit systems see the 64-bit SYSTEM32 (sic) directory as C:\Windows\SysNative
However, the following works from both 32-bit and 64-bit sessions:
$is64Bit = Test-Path 'Env:ProgramFiles(x86)'
Only on 64-bit systems does an automatically defined ProgramFiles(x86) environment variable exist alongside the ProgramFiles variable.
To get the OS-native pointer size in bytes:
$pointerSizeInBytes = (4, 8)[[bool] ${env:ProgramFiles(x86)}]
${env:ProgramFiles(x86)} uses namespace variable notation to return the value of env. var. ProgramFiles(x86) directly; casting a string value to [bool] returns $true only for non-empty strings; a [bool] interpreted as an array index ([int]) maps to either 0 ($false) or 1 ($true), which is used here to select the appropriate value from array 4, 8.
Another approach is to wrap it inside a small helper function:
function Get-Architecture {
# What bitness does Windows use
$windowsBitness = switch ([Environment]::Is64BitOperatingSystem) { # needs .NET 4
$true { 64; break }
$false { 32; break }
default {
(Get-WmiObject -Class Win32_OperatingSystem).OSArchitecture -replace '\D'
# Or do any of these:
# (Get-WmiObject -Class Win32_ComputerSystem).SystemType -replace '\D' -replace '86', '32'
# (Get-WmiObject -Class Win32_Processor).AddressWidth # slow...
}
}
# What bitness does this PowerShell process use
$processBitness = [IntPtr]::Size * 8
# Or do any of these:
# $processBitness = $env:PROCESSOR_ARCHITECTURE -replace '\D' -replace '86', '32'
# $processBitness = if ([Environment]::Is64BitProcess) { 64 } else { 32 }
# Return the info as object
return New-Object -TypeName PSObject -Property #{
'ProcessArchitecture' = "{0} bit" -f $processBitness
'WindowsArchitecture' = "{0} bit" -f $windowsBitness
}
}
Get-Architecture
This will return both the 'bitness' of the currently running PowerShell process aswell as the bitness of the OS like:
ProcessArchitecture WindowsArchitecture
------------------- -------------------
64 bit 64 bit
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.
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
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*"}