I have many servers I need to remove Symantec Endpoint Protection from. I contacted Symantec and received the code below:
(Get-WmiObject -Class Win32_Product -Filter "Name='Symantec Endpoint Protection'" -ComputerName xxxxxx).Uninstall()
I have used it and it worked on 10 servers no problem at all. I tried it again today and am getting the error:
You cannot call a method on a null-valued expression.
At line:1 char:1
+ (Get-WmiObject -Class Win32_Product -Filter "Name='Symantec Endpoint Protection' ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
Nothing has changed from when I started and am trying to figure out what the above error means. Also if I can get this to work does anyone see a way to add many servers to a foreach command or something.
Two things from my side:
1) you should not use win32_product cause it is broken to a certain level.
It is very slow alsso because it scans the entire thing.
2) In your case, "Name='Symantec Endpoint Protection'" reports Null for you which means that the value is not there. Please check the proper name.
For better performance and as part of enhancement , you should use the registry to fetch the details.
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()
}
}
}
Note: Use the function or the query inside the function to get the result.
Hope it helps.
Related
I wrote a GUI script that clones VMs from a template and gives them a static IP address that they get from a .csv file.
Everything seems to work fine except for an error I'm getting. The clone completes successfully anyway, but I'm not sure how to fix the error or if I even should.
function StartClone {
$VM_List = Import-Csv $csvTB.Text
$numClones = [int]((Get-Content $csvTB.Text).Length)
$vmh = Get-VMHost
$NewParameters = #{
# Name = ''
Template = $TemplateMenu.Text
Datastore = $DatastoreMenu.Text
DiskStorageFormat = 'Thin'
Location = $FolderCB.Text
OSCustomizationSpec = $CustomizationCB.Text
VMHost = Get-Random -InputObject $vmh
Server = $VCenterTB.Text
RunAsync = $true
}
$SetParameters = #{
NumCpu = $CPU_TB.Text
MemoryGB = $RAM_TB.Text
Notes = $NotesTB.Text
Confirm = $false
}
$taskList = if ($NumClones -gt 0) {
# foreach ($item in (Import-Csv $csvTB.Text))
$VM_List | ForEach-Object {
$NewParameters['Name'] = "$($_.Hostname)"
Get-OSCustomizationSpec -name $CustomizationCB.Text | Get-OSCustomizationNICMapping | Set-OSCustomizationNICMapping -IPMode UseStaticIP -IPAddress "$($_.IP)" -SubNetMask "$($_.Subnet)" -DefaultGateway "$($_.Gateway)" -Dns ""
New-VM #NewParameters
}
}
$newVM = $taskList | Wait-Task -ErrorAction SilentlyContinue
$newVM | Set-VM #SetParameters
$newVM | Get-NetworkAdapter | Set-NetworkAdapter -NetworkName $VLAN_CB.Text -Confirm:$false
if ($StartVM_CB.Checked -eq $true) {$newVM | Start-VM }
}
The error I get is related to the Wait-Task command which I have to use to wait for the VMs to actually be done cloning.
Wait-Task : The input object cannot be bound to any parameters for the command either because
the command does not take pipeline input or the input and its properties do not match any of
the parameters that take pipeline input.
At line:465 char:26
+ $newVM = $taskList | Wait-Task
+ ~~~~~~~~~
+ CategoryInfo : InvalidArgument: (UseStaticIP:192.168.1.1:PSObject) [Wait-Tas k], ParameterBindingException + FullyQualifiedErrorId : InputObjectNotBound,VMware.VimAutomation.Common.Commands.Cmdlets. WaitTask
The error repeats for each VM in my .csv file, so if I have 4 VMs that I want to deploy, it will pop up 4 times. As the error suggests, it's related to the fact I'm looping over the .csv file and creating a new VM with with iteration, but like I said, everything completes without issues and the VMs are working.
Any input on this would be great. Thanks.
New-VM returns a VirtualMachineImpl object, and Wait-Task has nothing to do it...
If you add the -RunAsync parameter to New-VM it will return a TaskImpl object, then you can pipe the results to the Wait-Task cmdlet.
On two different machines joined to the same Windows 2008 R2 Active Directory domain, a Windows 7 workstation and a Windows 2008 R2 server, I am getting the following error when running a PowerShell script written by a Microsoft Field Engineer I downloaded from the Microsoft TechNet Gallery:
PS C:\Users\User1\Desktop> .\Find-PossibleMissingSPN.ps1
Get-ADObject : A parameter cannot be found that matches parameter name 'PipelineVariable'.
At C:\Users\User1\Desktop\Find-PossibleMissingSPN.ps1:37 char:114
+ Get-ADObject -LDAPFilter $filter -SearchBase $DN -SearchScope Subtree -Proper
ties $propertylist -PipelineVariable <<<< account | ForEach-Object {
+ CategoryInfo : InvalidArgument: (:) [Get-ADObject], ParameterBi
ndingException
+ FullyQualifiedErrorId : NamedParameterNotFound,Microsoft.ActiveDirectory
.Management.Commands.GetADObject
Various google searches have not yielded an answer. Does anyone know how to resolve this? Here's the actual code:
#.Synopsis
# To find possibly missing SPN registrations due to manual mistakes.
[CmdletBinding()]
Param
(
# start the search at this DN. Default is to search all of the domain.
[string]$DN = (Get-ADDomain).DistinguishedName
)
#
# define the SPN service classes to look for. Other types are mostly automated and should be OK.
#
$servicesclasses2check = #("host", "cifs", "nfs", "http", "mssql")
#
# get computers and users with a nonzero SPN within the given DN.
#
$filter = '(&(servicePrincipalname=*)(|(objectcategory=computer)(objectcategory=person)))'
$propertylist = #("servicePrincipalname", "samaccountname")
Get-ADObject -LDAPFilter $filter -SearchBase $DN -SearchScope Subtree -Properties $propertylist -PipelineVariable account | ForEach-Object {
#
# Create list of interesting SPNs for each account. Strong assumption for all code: SPN is syntactically correct.
#
$spnlist = $account.servicePrincipalName | Where-Object {
($serviceclass, $hostname, $service) = $_ -split '/'
($servicesclasses2check -contains $serviceclass) -and -not $service
}
#
# Look for cases where there is no pair of (host, host.domain) SPNs.
#
foreach ($spn in $spnlist)
{
($serviceclass, $hostname, $service) = $spn -split '/'
if ($service) { $service = "/$service" }
($fullname, $port) = $hostname -split ':'
if ($port) { $port = ":$port" }
($shortname, $domain) = $fullname -split '[.]'
#
# define the regexp matching the missing SPN and go look for it
#
if ($domain) {
$needsSPN = "${serviceclass}/${shortname}${port}${service}`$"
$needsSPNtxt = "${serviceclass}/${shortname}${port}${service}"
} else {
$needsSPN = "$serviceclass/${shortname}[.][a-zA-Z0-9-]+.*${port}${service}`$"
$needsSPNtxt = "$serviceclass/${shortname}.<domain>${port}${service}"
}
#
# search the array of SPNs to see if the _other_ SPN is there. If not, we have problem case.
#
if (-not ($spnlist -match $needsSPN))
{
[PSCustomobject] #{
samaccountname = $account.samaccountname
presentSPN = $spn
missingSPN = $needsSPNtxt
}
}
}
}
The -PipelineVariable common parameter is only available in PowerShell v4+. You need to upgrade to a later version for this to work.
I have this function I'm using a foreach statement block to run against a number of machines:
function Get-InstalledApps ($appStr) {
$appWC = "*$appStr*"
if ([IntPtr]::Size -eq 4) {
$regpath = 'HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*'
}
else {
$regpath = #(
'HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*'
'HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*'
)
}
$getapps = Get-ItemProperty $regpath | .{process{if($_.DisplayName -and $_.UninstallString) { $_ } }}
Foreach ($app in $getapps | where {$_.DisplayName -like $appWC}) {
[pscustomobject]#{Computer = ($env:COMPUTERNAME + "." + $env:USERDNSDOMAIN)
AppName = ($app.displayname)
Publisher = ($app.Publisher)
DisplayVersion = ($app.DisplayVersion)
InstallDate = ($app.InstallDate)
UninstallString = ($App.UninstallString)}
}
}
Locally, it looks like this:
PS C:\windows\system32> Get-InstalledApps ibm | ft
Computer AppName Publisher DisplayVersion InstallDate UninstallString
-------- ------- --------- -------------- ----------- ---------------
Computer.domain.COM IBM Tivoli Storage Manager Client IBM 06.04.0001 20140807 MsiExec.exe /I{FF99015E-71B4-41AB-8985-67D99383A72A}
But when run remotely on some computers
(i.e:)
Invoke-Command -ComputerName $computer -ScriptBlock
${function:Get-InstalledApps} -ArgumentList $appStr
I get the above, however on others I get this:
Name Value
---- -----
UninstallString MsiExec.exe /I{68C09095-AC00-4541-B46B-0835F2BDB0CE}
Computer comp1.domain.com
Publisher IBM
InstallDate 20150122
DisplayVersion 07.01.0000
AppName IBM Tivoli Storage Manager Client
UninstallString MsiExec.exe /X{1316AC9A-7A5D-4866-B41F-4B3CF03CE52A}
Computer comp2.domain.com
Publisher IBM Corp.
InstallDate 20170226
DisplayVersion 9.2.7.53
AppName IBM BigFix Client
Without having a chance to verify PowerShell versions of some of the computers yet, I'm guessing the 2nd set of results may be as a result of being run against computers running < version 3.0.
Any way to force the output to display as a table (1st example output) on all computers?
I'm guessing the 2nd set of results may be as a result of being run against computers running < version 3.0.
If you are running that on systems that are not at least version 3 then your [pscustomobject] cast would fail since that was introduced in v3. I would have expected that to just trigger an error but instead it appears to be returning the hashtable. A compatible solution would be to use new-object instead.
New-Object -TypeName PSCustomObject -Property #{
Computer = ($env:COMPUTERNAME + "." + $env:USERDNSDOMAIN)
AppName = ($app.displayname)
Publisher = ($app.Publisher)
DisplayVersion = ($app.DisplayVersion)
InstallDate = ($app.InstallDate)
UninstallString = ($App.UninstallString)
}
Thanks Matt.
That worked, which is my preferred method.
if the app wasn't installed or the host was offline, a couple of variations of IF statements didn't seem to pick up the output at another point in the script (only displayed if it was installed) and returned as a blank line, however this seemed to be picked up by the statement blocks:
function Get-InstalledApps ($appStr) {
$appWC = "*$appStr*"
if ([IntPtr]::Size -eq 4) {
$regpath = 'HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*'
}
else {
$regpath = #(
'HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*'
'HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*'
)
}
$getapps = Get-ItemProperty $regpath | .{process{if($_.DisplayName -and $_.UninstallString) { $_ } }}
$getapps | where {$_.DisplayName -like $appWC} | Select #{n='Computer';e={$env:COMPUTERNAME + "." + $env:USERDNSDOMAIN}},Displayname,Publisher,DisplayVersion,InstallDate,UninstallString
}
I'm trying to get the username of domain users in a PowerShell logon script. Any number of different users may log into the computers in question.
A local user account (let's call it 'syscheck') is configured on Win7/Win8 domain clients for the purpose of running a PS script (PS 2.0/3.0); the script resides locally and is launched by Task Scheduler on user logon. The script needs to obtain the username of the domain user that is logging in.
I've attempted to do this with WMI:
Get-WmiObject Win32_ComputerSystem | Select-Object -ExpandProperty UserName
but this does not return anything when the script runs.
If I try this:
$env:USERNAME
The username of the 'syscheck' local account is returned.
Is the domain username not yet available when the script is running on logon?
Perhaps there a way to do this with .NET? Other options?
***** UPDATE August 8 *****
I've tested with the solution provided (thanks Alexander!) but still can NOT retrieve the username of the logged-in user. I believe this is because, as mentioned above, this is a logon script launched by Task Scheduler. The principal for the Task that launches the script is a local account. For some reason, all methods of trying to get the domain username fail.
Here is latest attempt:
First, this is how I call the function:
$indx = 0
do {
$username = GetDomUser
if (($indx -eq 25) -or ($username.Length -ne 0)) {
Write-Output $username
Break
}
else {
Start-Sleep -Seconds 12
}
$indx++
}
while ($indx -lt 25) # 5 minutes is PLENTY of time for boot...
Now, here's the function:
Function GetDomUser {
$compname = $($env:COMPUTERNAME)
$pattern = '"MYDOMAIN",Name='
$antecedent = #(Get-WmiObject -Class Win32_LoggedOnUser -ComputerName $compname |
Where-Object { $_.Antecedent -match $pattern } | Select-Object -ExpandProperty Antecedent)
Return ([regex]::Match([string]$antecedent[0],"$pattern(.*$)").Value).Split('=')[1] -replace '"', ""
}
Of course, this works perfectly from the console once the machine has booted.
Is it possible to refresh whatever store the Win32_LoggedOnUser Class gets its data from?
Other options?
Here are previous methods I've tried - all return the username of the principal of the Task that launches the script (or an empty string, which is what D returns).
$usernameA = $([System.Security.Principal.WindowsIdentity]::GetCurrent().Name)
$usernameB = $(whoami)
$usernameC = $($env:USERNAME)
$usernameD = $(Get-WmiObject Win32_ComputerSystem -ComputerName $compname | Select-Object -ExpandProperty UserName)
$usernameE = $([Environment]::UserName)
Here's what you could do to find out what's going on:
$iLOGON32_LOGON_INTERACTIVE = 2
$cLogonSessions = Get-WmiObject -Class "Win32_LogonSession" `
| Where-Object { $_.LogonType -eq $iLOGON32_LOGON_INTERACTIVE }
if ($cLogonSessions -ne $null) {
$cInteractiveLogons = #()
foreach ($oLogonSession in $cLogonSessions) {
$sWmiQuery = ('ASSOCIATORS OF {{Win32_LogonSession.LogonId="{0}"}} ' `
+ 'WHERE AssocClass=Win32_LoggedOnUser') -f $oLogonSession.LogonId
$cInteractiveLogons += Get-WMIObject -Query $sWmiQuery `
| Select-Object -ExpandProperty "Caption"
}
} else {
$ex = New-Object -TypeName System.NullReferenceException(`
'$cInteractiveLogons is null.')
throw $ex
}
$cInteractiveLogons | Select-Object -Unique
When $cInterativeLogons is null exception is thrown, it means that no-one is logged on interactively (yet) in which case you can wait and re-check later.
Note that this code is not reliable because LOGON32_LOGON_INTERACTIVE wasn't limited to local console logons in XP and earlier versions.
As for actual solution, I'd recommend using some kind of explicit notifications. You could for example make use of events. Subscribe for an event and then emit the event from the user's regular logon script.
The problem was not with the WMI code but rather the state of the machine it was being run on. It turns out that when users are VPNed into their machines (almost always thanks to a VPN client's automated reconnect feature), or have some third-party utility installed (e.g. certain cloud backup services), there are multiple Logons and "the" logged on user is ambiguous.
For now this is working pretty well:
Function GetDomainUser {
$compname = $($env:COMPUTERNAME)
$pattern = '"' + $($env:USERDOMAIN) + '"' + ',Name='
$antecedent = #(Get-WmiObject -Class Win32_LoggedOnUser -ComputerName $compname |
Where-Object { $_.Antecedent -match $pattern } |
Select-Object -ExpandProperty Antecedent | Select-Object -Unique)
Return $(([regex]::Match([string]$antecedent,$($pattern + '(".+")')).Value).Split('=')[1] -replace '"','')
}
But I had to write addition code to work around cases when the LoggedOnUser cannot be discovered (multiple logons exist), or when no one is logged in.
I am generating a report where I need to find which servers has mountpoints configured on it..
can you help how to get that infor using WMI or powershell.
I mean I need to identify the servers, if mountpoints exists in it.. and also their names....
Get a list of all servers from textfile, AD, etc. and run a foreach loop with something like this:
Get-Wmiobject -query “select name,driveletter,freespace from win32_volume where drivetype=3 AND driveletter=NULL” -computer servername
A quick google search for "windows mount point wmi" would return THIS (source).
Then export the results to CSV, HTML or whatever you need. Your question is lacking a lot of details and any sign of effort from your part, so I can't/won't go any further.
UPDATE: Does this help? It lists mount points(folder paths, not driveletters).
$servers = #("server1","server2","server3","server4","server5")
$servers | % {
$mountpoints = #(Get-WmiObject Win32_MountPoint -ComputerName $_ | Select-Object -ExpandProperty Directory | ? { $_ -match 'Win32_Directory.Name="(\w:\\\\.+)"' }) | % { [regex]::Match($_,'Win32_Directory.Name="(\w:\\\\.+)"').Groups[1].Value -replace '\\\\', '\' }
if($mountpoints.Count -gt 0) {
New-Object psobject -Property #{
Server = $_
MountPoints = $mountpoints
}
}
}
Server MountPoints
------ -----------
{server1} {D:\SSD, C:\Test}