I have a namespace that I'd like to use to validate the existence of a WMI object before I run a Get-WmiObject against it further down in the code. For example, I want to throw the namespace for SSRS 2012 at it and if it doesn't exist on the machine, then I'll try the next namespace for SSRS 2008 R2.
Is there a way to check for the class's existence, by just guessing a namespace, without throwing an error if it does not exist?
I don't want to have to use a try-catch as the solution. I'd like to know a way that I can get a simple boolean result that tells me whether the class exists in this namespace.
I don't want to have to use SilentlyContinue as the solution either.
This will be executed from a Powershell job step in a SQL Agent job. This sometimes handles errors differently than pure Powershell, and is the reason for my concern about #'s 1 and 2 above.
You can use the -Class, -List, and -Namespace parameters of the Get-WmiObject cmdlet to see if a single class exists in the specified namespace:
$class = Get-WmiObject -Class 'Win32_BIOS' -List -Namespace 'root\cimv2';
$classExists = $class -ne $null;
Here's an alternative (but slower) method from an earlier revision of my answer:
$class = Get-WmiObject -List -Namespace 'root\cimv2' `
| Where-Object { $_.Name -eq 'Win32_BIOS'; };
$classExists = $class -ne $null;
Going back to my original answer, here's a third option that, in my testing, does not throw any errors if either the namespace or the class is invalid:
$class = Get-WmiObject -List | Where-Object {
$_.__NAMESPACE -eq 'root\cimv2' -and $_.__CLASS -eq 'Win32_BIOS';
};
$classExists = $class -ne $null;
Note that $_.Name and $_.__CLASS are effectively synonyms. In each of these code snippets, $class will contain a ManagementClass instance for the class you searched for, if found, otherwise $null.
Related
I have the following script I wrote using PowerShell 5 that utilizes the Active Directory and Join-Object PowerShell modules to get a list of all AD Groups and their users (along with some additional properties per user like their manager and title):
$ADGroupsList = #(Get-ADGroup -Filter * -Properties * | Select-Object DistinguishedName,CN,GroupCategory,Description | Sort-Object CN)
#I'm using an ArrayList here so that later on I can use the .Add() method to avoid costly += operations.
$ADUsersList = New-Object -TypeName "System.Collections.ArrayList"
$ADUsersList = [System.Collections.ArrayList]#()
$Record = [ordered] #{
"Group Name" = ""
"Employee Name" = ""
"Title"= ""
"Manager" = ""
}
foreach ($Group in $ADGroupsList) {
$ArrayofMembers = #(Get-ADGroupMember -Identity $Group.DistinguishedName | Where-Object { $_.objectClass -eq "user" })
#Loop through each member in the list of members from above
foreach ($Member in $ArrayofMembers) {
#Get detailed user info about the current user like title and manager that aren't available from Get-ADGroupMember
$User = #(Get-ADUser -Identity $Member -Properties name,title,manager | Select-Object Name, Title, #{Label="Manager";Expression={(Get-ADUser (Get-ADUser $Member -Properties Manager).Manager).Name}})
#Specifies what values to apply to each property of the $Record object
$Record."Group Name" = $Group.CN
$Record."Employee Name" = $Member.Name
$Record."Title" = $User.Title
$Record."Manager" = $User.Manager
#Put all the stored information above in a 'copy' record
$objRecord = New-Object PSObject -property $Record
#Add that copy to the existing data in the ADUsersList object
[void]$ADUsersList.Add($objRecord)
}
#Using Join-Object here to enable me to use SQL-like JOINs
Join-Object -Left $ADUsersList -Right $ADGroupsList -LeftJoinProperty "Group Name" -RightJoinProperty "CN" -Type AllInLeft -LeftMultiMode DuplicateLines -RightMultiMode DuplicateLines -ExcludeRightProperties DistinguishedName | Export-Csv ("C:\ADReports\" + $Group.CN + " Report.csv") -NoTypeInformation
$ADUsersList.Clear()
}
Here's the output I expect (columns may be out of order, but column ordering isn't important):
My code works great for most groups, but for groups that have only one member (or none), I get an error:
Join-Object : Method invocation failed because [System.Management.Automation.PSCustomObject] does not contain a method named 'ForEach'.
At C:\GetADGroups&Users.ps1:54 char:5
+ Join-Object -Left $ADUsersList -Right $ADGroupsList -LeftJoinProp ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (ForEach:String) [Join-Object], RuntimeException
+ FullyQualifiedErrorId : MethodNotFound,Join-Object
At first, I thought it was because I read arrays/arraylists with one entry get turned into scalars. But a knee-jerk wrapping of every object I can think of in #() didn't resolve the issue. In fact, if I wrap the $objRecord assignment (New-Object PSObject -property $Record) in #() to convert it to an array, it writes the Member Properties of $ADUsersList to the Join-Object line instead of the contents of $ADUsersList, resulting in this:
Is there somewhere I've missed an array/arraylist getting converted to a scalar? Why is the code above throwing an error for groups with <= 1 entries?
Compounding my curiosity, PowerShell 7 (possibly 6, too) doesn't seem to care about this issue; it doesn't throw the error at all (instead it just outputs the appropriate single-value/blank CSV). Normally I'd just wipe my hands and say PS 7 is required, but I'd like to get this working in PowerShell 5, or at least understand what is causing the issue.
Googling led me to several related articles & questions, including:
Method Invocation .Foreach failed, System.Object doesn't contain a method named 'foreach' this one's specific to PowerShell v2 (I'm running v5)
Method invocation failed because [System.Management.Automation.PSObject] doesn't contain a method named 'op_Addition' this one seems only tangentially related. Incidentally it's where I read that arrays with one item output as scalars, as I mentioned earlier.
It does appear that scalars lack the .ForEach() & .Where() methods in 5.1. The additional of the methods is probably just an enhancement newer version, certainly 7 not sure about 6. I'm sure that's documented somewhere.
I can't really test your code but it doesn't look like there's anywhere that could be flipping to a scalar. To help guarantee ArrayList collections through out you can type constrain the variables like [Collections.ArrayList]$Var = #() This may end up being more practical than hunting for an implementing #() throughout.
Something that stands out is the error seems to come from Join-Object I only found a single invocation of .ForEach() on line 820 of Join-Object.ps1 My guess is it's this line or similar elsewhere in the module combined with the 5.1 runtime environment.
If you can manually modify that to a traditional | ForEach-Object {...} might be telling. And/or you can wrap $result like #($Result) right before the .ForEach() is invoked.
Really interested to see what you come up with. I see you've already posted an issue with the author. Please post back if you get a reply. Thanks.
I'm trying to rename existing AD groups in this way.
AD groups starting # to be renamed to the same name without #. For example , I have #dl1 and I wish to get it renamed dl1 (omitting #)
Im trying to rename following four users first.
I have written two arrays, in this manner. ($myArray and $myArray2).
$myArray =#(
$data = Get-ADGroup -Filter {name -like "#*"} |select samaccountname
$data.samaccountname | foreach {$_.split("#")[1]
}
)
$myArray2 =#(
$assdf=Get-ADGroup -Filter {name -like "#*"}
$myArray2 =#($assdf)
$num=0
foreach($a in $assdf)
{
$myArray2[$num]
$num=$num+1
}
)
If I print $myarray it gives exact results, that I wish, in this way.
and also if I print $myarray2 it gives the desired results in this way,
the missing piece of the puzzle is combining those two arrays to run the final command that is
set-adgroup -identity (members indide $myArray2) -samaccountname (members indide $myArray)
For hours, I have tried numerous methods to get set-adgroup .. using for each loop etc.
for example,
$a=0
foreach ($item in $myArray2)
{
$nameto_replace=$myArray[$a]
Set-adgroup -identity $item.samaccountname -samaccountname $nameto_replace
$a=$a+1
}
Can anyone please shed some light, please? I am totally out of ideas now. thanks in advance
There is no need to perform Get-ADGroup twice, where you can use it once and loop over the results in a ForEach-Object loop:
Updated as per Aravinda's helpful observation
Get-ADGroup -Filter "Name -like '#*'" | ForEach-Object {
$newName = $_.Name.TrimStart('#')
Write-Host "Renaming group $($_.Name).. to '$newName'"
# replace only the SamAccountName
$_ | Set-ADGroup -SamAccountName $newName
# or replace multiple properties at the same time.
# You need to use the LDAP names here, so mind the casing !
# See http://www.selfadsi.org/group-attributes.htm
# $_ | Set-ADGroup -Replace #{sAMAccountName = $newName; displayName = $newName}
}
You can limit the search to a specified OU if you want by adding the OU's DistinguishedName with the -SearchBase parameter
Theo's answer is fantastic!
Following is the one finally I used derived from theo's answer.
Get-ADGroup -Filter "Name -like '#*'" | ForEach-Object {
$newName = $_.Name.TrimStart('#')
$_ | Set-ADGroup -Replace #{sAMAccountName = $newName;displayName = $newName}
$_ | Rename-ADObject -NewName $newName
}
If you try using set-adgroup to change 'name' and 'CN' and it gives below error.
"Set-ADGroup : The directory service cannot perform the requested operation on the RDN attribute of an object"
To change multiple attributes, especially including Name and CN, combination of Rename-ADObject and Set-ADGroup can be used.
Currently working on making a new report that will be generated with PowerShell. Using PowerShell to build a HTML email. I have one other report working fine but ran into an unexpected issue on this one.
The below code is just s sample from the script I am still building. Still adding pieces to the script but testing it as I move forward. I added a Test-Connection to see if a computer was responding or not and lost the ability to build an array.
My final goal with this report is to import a list of names from a file and then loop over all of the computers to see if they are pinging and gather some information from them using Get-WMIObject, etc.
The below code will replicate the issue I am having but I am not sure how to solve it. I've narrowed down the issue to when Test-Connection returns 'False'. On line 26 I am filtering for just results that returned a 'False' on Test-Connection to save them into its own array so that I can use that array in a different part of my code to build the HTML table/HTML to send out the email.
Only the flipside, if I tell it to look for only 'True', it will save into the array without issue.
This is the error that PowerShell is giving when doing filtering by 'False'.
Cannot convert value "#{Computer_Name=Computer1; Ping_Status=False}" to type "System.Collections.ArrayList". Error: "Cannot convert the "#{Computer_Name=Computer1 Ping_Status=False}" value of type "Selected.System.Management.Automation.PSCustomObject" to type "System.Collections.ArrayList"."
Please let me know if there is any other information that I can provide. I've been stuck on this one for a while. Co-workers are even say this is a weird one.
Is there something unique about the way Test-Connection return a 'False'?
CLS
[string]$ErrorActionPreference = "Continue"
[System.Collections.ArrayList]$Names = #(
"Computer1"
"Computer2"
)
[System.Collections.ArrayList]$WMI_Array = #()
[System.Collections.ArrayList]$Ping_Status_False = #()
foreach ($Name in $Names) {
[bool]$Ping_Status = Test-Connection $Name -Count 1 -Quiet
$WMI_Array_Object = [PSCustomObject]#{
'Computer_Name' = $Name
'Ping_Status' = $Ping_Status
}
$WMI_Array.Add($WMI_Array_Object) | Out-Null
}
$WMI_Array | Format-Table
[System.Collections.ArrayList]$Ping_Status_False = $WMI_Array | Where-Object {$_.Ping_Status -eq $false} | Select-Object Computer_Name, Ping_Status
$Ping_Status_False
The problem is not Test-Connection but that this statement
$WMI_Array | Where-Object {$_.Ping_Status -eq $false} | Select-Object Computer_Name, Ping_Status
produces just a single result. Which is not an array, and can thus not be converted to an ArrayList. The behavior is identical when you filter for $_.PingStatus -eq $true with just a single matching object, so I suspect that you had either more than one successfully pinged host or none at all when you tested that condition and it didn't throw the same error.
You could mitigate the problem by wrapping the statement in the array subexpression operator:
[Collections.ArrayList]$Ping_Status_False = #($WMI_Array |
Where-Object {$_.Ping_Status -eq $false} |
Select-Object Computer_Name, Ping_Status)
Or, you could simply drop all the pointless type-casting from your code:
$ErrorActionPreference = "Continue"
$Names = 'Computer1', 'Computer2'
$WMI_Array = foreach ($Name in $Names) {
[PSCustomObject]#{
'Computer_Name' = $Name
'Ping_Status' = [bool](Test-Connection $Name -Count 1 -Quiet)
}
}
$WMI_Array | Where-Object { -not $_.Ping_Status }
I'm trying to compare ACLs on a folder with a reference set of ACLs, and then list any exceptions. The "fuzzy" part of the equation is that I want to be able to disregard any unknown SID. So creating a reference folder with the perms I want to test won't work to use Compare-Object between it and my test folder.
The underlying scenario is that I am cleaning up old user directories where the actual user account has been deleted (this is where the non-resolved SID comes in). By default, the folders include perms for Administrator and the like, which I don't care about. There are some folders, however, where another user has been granted explicit permissions, and I want to capture these. Unfortunately, there aren't any shortcuts I can use to check: e.g. -IsInherited or the like to exclude ACLs I don't care about.
Per the below, I can dump the ACLs out into an array
$acl = get-acl f:\user_folder
$access = $acl.Access | ForEach-Object { $_.identityReference.value }
$access
BUILTIN\Administrators
MYDOMAIN\JBLOGGS
S-1-5-21-4444444444-9999999-1111111111-74390
MYDOMAIN\Domain_Group ###Yes, the group has an underscore in the name
I can create another array of the users I want to ignore, including a partial string to match any unresolved SID.
$defaults = #("BUILTIN\Administrators","MYDOMAIN\DomainGroup","S-1-5-21")
So how do I compare my $defaults array with the $access array and output only the exceptions like "MYDOMAIN\JBLOGGS"?
I'm trying a foreach, but I'm stumped about grabbing that exception. The following still outputs the SID I want to avoid. I'm hoping to also avoid too many nested "IFs".
$access | ForEach { If ($defaults -notcontains $_) { Write-Output $_ } }
MYDOMAIN\JBLOGGS
S-1-5-21-4444444444-9999999-1111111111-74390 #Do not want!
If I put the wildcard $_* into the -notcontains, I get the whole contents of $access again.
I'd do something like this:
$defaults = 'BUILTIN\Administrators', 'MYDOMAIN\DomainGroup', 'S-1-5-21*'
$acl.Access | Where-Object {
$id = $_.IdentityReference
-not ($defaults | Where-Object { $_ -like $id })
} | Select-Object -Expand value
$defaults | Where-Object { $_ -like $id } does a wildcard match of the given identity against all items of $defaults. The wildcard * at the end of S-1-5-21* allows to match all strings starting with S-1-5-21. The negation -not inverts the result so that only identities not having a match in $defaults pass the filter.
give the users you want to ignore some right on a dummy folder, get the acl of that folder and then compare whith the acl of your actual folder
$genericACL = get-acl c:\temp\dummy
$folderacl = get-acl f:\user_folder
$exceptions= $folderacl.Access.identityreference.value |?{ ($_ -notin $genericACL.access.identityreference.value) -and ($_.strartswith('S-1-5-21') -eq $false)) }
In the end, it was fairly simple, thanks to the help above.
I managed to omit the fact in the original question where I required it to work in Powershell v2.
$defaults = #("BUILTIN\Administrators","MYDOMAIN\DomainGroup")
$acl = get-acl $folder
$access = $acl.Access | ForEach-Object { $_.identityReference.value }
# check that no other account still has access to the folder
$access | ForEach {
If ($defaultACL -notcontains $_ -and $_ -notlike 'S-1-5-21*') {
write-output "Extra perms:$user $_"
}
I'm playing with SMO and tried to use it to change the database owners to sa. The code is
# To simplify our discussing, let's say we have a function Get-SMOServer
$s = Get-SMOServer -Instance myserver\myinstance
$s.databases | ?{$_.owner -ne "sa"} | %{$_.setowner("sa", $true)}
At this point, when I check the database owners from SSMS, the owners were already changed. However, if I check it from $s.databases, I still got the old data, until I do something like:
$s.databases | %{$_.refresh()}
Then I can get the correct result from $s.databases.
I checked the SMO objects and found many of them have a refresh() function. My question is, should I call refresh() every time I modified some object? How to find all object types that have a refresh() member?
Thanks
You can look at the SMO assembly. I'm using SMO which ships with 2008 R2:
$assm = add-type -AssemblyName "Microsoft.SqlServer.Smo, Version=10.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91" -EA Stop -PassThru
$assm | %{$hasRefreshMethod = $null; $hasRefreshMethod = $_.GetMethods() | ?{$_.name -eq "Refresh"}; new-object psobject -property #{Name=$_.Name; HasRefreshMethod=$($hasRefreshMethod -ne $null)}}