How can I cast as a user object in PowerShell? - powershell

I've got the following code which connects to every computer on the domain and checks the members of the local administrators group:
Foreach ($Computer in Get-ADComputer -Filter *){
$Path = $Computer.Path
$Name = ([ADSI]"$Path").Name
Write-Host $Name
$members = [ADSI]"WinNT://$Name/Administrators"
$members = #($members.psbase.Invoke("Members"))
ForEach($member in $members){
Write-Host $member.GetType().InvokeMember("Name", 'GetProperty', $null, $member, $null)
Write-Host $member.GetType().InvokeMember("AdsPath", 'GetProperty', $null, $member, $null)
}
}
I'm trying to store the value of $member in a $User object of some sort, so I can actually reference the attributes without all the crazy invoker stuff.
E.g., in pseudocode I want:
$user = (User) $member;
Write-Host $user.Name
Write-Host $user.AdsPath
I'm new to PowerShell, however... and I'm not sure if I really understand how to cast to an object type within it.

You want to create a custom object (or PSObject) with the specified members and values:
Foreach ($Computer in Get-ADComputer -Filter *){
$Path=$Computer.Path
$Name=([ADSI]"$Path").Name
write-host $Name
$members =[ADSI]"WinNT://$Name/Administrators"
$members = #($members.psbase.Invoke("Members"))
ForEach($member in $members){
$propsWanted = #('Name' , 'AdsPath') # An array of the properties you want
$properties = #{} # This is an empty hashtable, or associative array, to hold the values
foreach($prop in $propsWanted) {
$properties[$prop] = $member.GetType().InvokeMember($prop, 'GetProperty', $null, $member, $null)
}
$user = New-Object PSObject -Property $properties
$user # This is an object representing the user
}
}
Just to go over some of the changes:
I'm putting all of the property names you want into an array $propsWanted, and then iterating over that to invoke and get each one. This lets you easily work with more properties later, by adding the property names in a single place.
The $properties hashtable will store a key/value pair, where the key is the property name, and the value is the property value.
Once the hashtable is filled, you can pass it to the -Property parameter of New-Object when creating a PSObject.
You should use your new object and have a look at by testing a few things:
Pipe it to the Format- cmdlets to see various views:
$user | Format-Table
$user | Format-List
Check the values of its properties directly:
$user.Name
$user.AdsPath
If you make an array of these objects, you can filter them for example:
$user | Where-Object { $_.Name -like '*Admin' }
Alternatives
You might try using CIM or WMI, which will actually be a little friendlier:
CIM
$group = Get-CimInstance -ClassName Win32_Group -Filter "Name = 'Administrators'"
Get-CimAssociatedInstance -InputObject $group -ResultClassName Win32_UserAccount |
select -ExpandProperty Caption
Get-CimAssociatedInstance -InputObject $group -ResultClassName Win32_Group |
select -ExpandProperty Caption
WMI
$query = "ASSOCIATORS OF {Win32_Group.Domain='$($env:COMPUTERNAME)',Name='Administrators'} WHERE ResultClass = Win32_UserAccount"
Get-WmiObject -Query $query | Select -ExpandProperty Caption
$query = "ASSOCIATORS OF {Win32_Group.Domain='$($env:COMPUTERNAME)',Name='Administrators'} WHERE ResultClass = Win32_Group"
Get-WmiObject -Query $query | Select -ExpandProperty Caption
.NET 3.5 Required for this method:
Add-Type -AssemblyName System.DirectoryServices.AccountManagement
$ctype = [System.DirectoryServices.AccountManagement.ContextType]::Machine
$context = New-Object -TypeName System.DirectoryServices.AccountManagement.PrincipalContext -ArgumentList $ctype, $env:COMPUTERNAME
$idtype = [System.DirectoryServices.AccountManagement.IdentityType]::SamAccountName
$group = [System.DirectoryServices.AccountManagement.GroupPrincipal]::FindByIdentity($context, $idtype, 'Administrators')
$group.Members |
select #{N='Domain'; E={$_.Context.Name}}, samaccountName
Attribution
All of the above alternatives were taken directly from this "Hey, Scripting Guy!" article, The Admin's First Steps: Local Group Membership. It goes into detail about all of these, including the [ADSI] method. Worth a read.
How to Actually Cast
I just realized I didn't actually answer this question, even though it's not exactly what you needed. Classes/types are specified with square brackets. In fact, when you did this:
[ADSI]"WinNT://$Name/Administrators"
You casted a [String] (the string literal in ") to an [ADSI] object, which worked because [ADSI] knows what to do with it.
Other examples:
[int]"5"
[System.Net.IPAddress]'8.8.8.8'
Since we don't know the type of the "user" object you're seeking (or it's not even really loaded), you can't use this method.

Related

No output to CSV using PowerShell psobject in Active Directory

I have this portion of code here that has worked in the past in multiple AD environments, however after testing within a new AD environment, I am getting no output to CSV or any errors being thrown. The size of the CSV file is always zero.
if (![string]::IsNullOrEmpty($searchbase))
{
$ADComputers = get-adcomputer -searchBase $searchbase -filter * -properties * -ResultPageSize $resultpagesize
}
else
{
$ADComputers=Get-ADComputer -Filter * -Properties * -ResultPageSize $resultpagesize
}
$data = #()
foreach ($computer in $ADComputers) {
$computer.member| foreach-object {$members += $_}
$computer.memberof | foreach-object {$memberof += $_}
$memstr = ($members -join ";")
$memstr2 = ($memberof -join ";")
$ADcomp = Get-ADComputer $computer -properties logonCount, ManagedBy | select-object logonCount, ManagedBy
$row = New-Object -TypeName psobject -Property #{
PrincipalID = $script:ctr;
logonCount=$ADcomp.logonCount;
ManagedBy=$ADcomp.ManagedBy;
}
$data += $row
$script:ctr++
}
$data | Export-Csv "ADComputers.csv" -NoTypeInformation
I'm not sure exactly where to go from here because I have tested multiple different options, so any help would be greatly appreciated!
The only reason you get no output is that $ADComputers has no elements. This may be related to a value in the variable $searchbase that does not exist or simply has no computer accounts in it.
But here are some general recommendations:
you do:
if (![string]::IsNullOrEmpty($searchbase))
you could also do:
If ($searchbase)
In principle if you have different scenarios to cover and so the parameter may change take a look at splatting.
Then you query all computers with all available properties but later in the loop you query the specific computer again which is not necessary. Also you should avoid adding elements to an array by using +=, this causes to rebuild the array each time which is slow.
Furthermore $computer.memberof is already an array holding the information but you pipe it to foreach and build a new array holding the identical information only to join it later to a string.
If this is not part of function I don't know why you raise the scope of the variable $ctr from local to script think this is not necessary.
Putting this all together you could do:
#Define HashTable for splatting
$parametersHt = #{
filer='*'
properties='*'
resultsetpagesize=$resultpagesize
}
#If searchbase is specified add parameter to splatting HashTable
If ($searchbase){
$parametersHt.add('Searchbase',$searchbase)
}
#Get computers
$ADComputers = get-adcomputer #parametersHt
#Set Counter
$ctr = 0
$data = #(
foreach ($computer in $ADComputers){
$ctr++
[PSCustomObject]#{
PrincipalId = $ctr #Really the counter here - not $computer.samaccountname?
logonCount=$computer.logonCount
manageddBy=$computer.ManagedBy
memberof=($computer.memberof -join ";") #added this, but in your code sample you don't return this value, if not needed remove. btw. a computer can be memberof a group but it can't have members
}
}
)
$data | Export-Csv -path ".\ADComputers.csv" -NoTypeInformation

Access objects using variable name powershell

I am unable to access my object using .PropertyName.
I have tried using $val.Options.$propertyName but it yields no result.
$propertyName is a value input from a file
`$val.$propertyName` results in "Cannot index into null array"
$result = New-Object -TypeName 'System.Collections.ArrayList';
foreach ($user in $users) {
$val = Get-LocalUser $user | Select *
$val = $val.$propertyName
$result.Add($val)
}
In your context $val.$propertyName does't mean anything can you try :
$result = New-Object -TypeName 'System.Collections.ArrayList';
foreach ($user in $users) {
$val = Get-LocalUser $user
$result.Add($val)
}
$result will be an array of "Localuser".
You don't need an arraylist at all. Just let PowerShell do the collecting by capturing the output inside the loop
$result = foreach ($user in $users) {
(Get-LocalUser $user).$propertyName
}
This is assuming your variable `$propertyName` contains a valid attribute name
While the above code does what you've asked for, I don't think the result would be very helpful, because it just lists whatever is in the property stored in $propertyName, and you cannot see which user has what values in there.
A better approach would be to output objects with the properties you want returned.
Something like
# just an example..
$propertyName = 'Description'
$users = 'WDAGUtilityAccount', 'Administrator'
$result = foreach ($user in $users) {
output an object with the name of the user, and also the property you are after
Get-LocalUser $user | Select-Object Name, $propertyName
}
$result
Since parameter -Name (the first positional parameter) can take an array of usernames, this can be shortened to:
$result = Get-LocalUser $users | Select-Object Name, $propertyName

find members of groups excluding disabled users

I have 10 security groups all that are called " 'companyname' RDS Users"
I am trying to create a script that does the following: List all the groups and then list all of the members excluding the disabled members, then have it email a csv. I have done the following but cant get the disabled user excluded.
The Script belows shows how far i got but the disabled users show in there which basically means the script is pointless.
$mailServer = ""
$mailFrom = ""
$mailTo = ""
$mailSubject = ""
$file = "somepath\RDSUsers.csv"
Import-Module ActiveDirectory
$US = Get-ADUser -Filter * -Property Enabled |where {$_.Enabled -eq "True"}| FT Name, Enabled -Autosize
$Groups = (Get-AdGroup -filter * | Where {$_.name -like "*RDS Users" -and $_.name -ne "RDS Users"}| select name -expandproperty name)
$Table = #()
$Record = [ordered]#{
"Group Name" = ""
"Name" = ""
"Username" = ""
}
Foreach ($Group in $Groups)
{
$Arrayofmembers = Get-ADGroupMember -identity $Group |select name,samaccountname
foreach ($Member in $Arrayofmembers)
{
$Record."Group Name" = $Group
$Record."Name" = $Member.name
$Record."UserName" = $Member.samaccountname
$objRecord = New-Object PSObject -property $Record
$Table += $objrecord
}
}
if ($Table -eq "RDS Users") {}
$Table
there is usualy a line here that sends the email with excel attachment
The following should produce the output you want in the $Table variable. You can then pipe $Table to one of the format-* commands.
Import-Module ActiveDirectory
$US = Get-ADUser -Filter "Enabled -eq '$true'" -Property Enabled
$Groups = Get-ADGroup -Filter "Name -like '*RDS Users' -and Name -ne 'RDS Users'" |
Select-Object -ExpandProperty Name
$Table = Foreach ($Group in $Groups)
{
try
{
$Arrayofmembers = Get-ADGroupMember -Identity $Group -ErrorAction Stop | Select-Object Name, SamAccountName
$compare = Compare-Object -ReferenceObject $US -DifferenceObject $Arrayofmembers -ExcludeDifferent -IncludeEqual -PassThru -Property SamAccountName -ErrorAction Stop |
Select-Object Name, SamAccountName
$compare | ForEach-Object {
[pscustomobject]#{
"Group Name" = $Group
"Name" = $_.Name
"UserName" = $_.SamAccountName
}
}
}
catch
{
[pscustomobject]#{
"Group Name" = $Group
"Name" = $null
"UserName" = $null
}
Continue
}
}
$Table
Explanation:
The Get-ADGroupMember command will not provide the Enabled property of its returned objects. You will need to feed its output into another command like Get-ADUser for that data. Since you already stored all of the enabled users in $US, we can simply compare $US collection to the results of each Get-ADGroupMember output.
I removed most of the Where-Object commands in favor of using the -Filter parameter on the AD commands. Almost always, the -Filter parameter will be faster especially when you are comparing AD indexed attributes like Name and Enabled.
You do not need to store each output object in a variable unless you are going to further manipulate it. This is why $Record was removed. Instead, all returned objects are stored in the array $Table. I removed the += operator mainly because of its inefficiency when repeatedly building arrays. Also, you can simply set a variable to the output of a foreach loop, which will result in the array you require. Since we created a custom object on each loop iteration and provided the properties at the time of declaration, [ordered] is not required. However, if you create the hash table first and then create a corresponding object, you will potentially need to use [ordered]. As an aside when you are creating custom objects that are involved in a loop, it is usually best practice to create a new object each time. Otherwise, you could unintentionally update values on the wrong objects. Just because you add an object to an array, you can still update its properties after the fact.
The Compare-Object command ties everything together. The -ExcludeDifferent -IncludeEqual parameter combination will only output objects with matching property values. Since we are comparing $Arrayofmembers and $US, that is ideal. The -PassThru switch allows the objects to be returned with all of the properties that were passed into the command. Then you can use the Select-Object command to pick which properties matter to you.

Can't get members of other domains and PS version

I am learning to create new objects and combine properties from other objects. In this script I want to find out what the PS version is and also add some other properties like OS, IP etc... but I am running into 2 problems. We have 6 domains and I can't seem to iterate over each domain. I tried (Get-ADForest).Domains and can see the list of domains. It still only returns objects in the domain my workstation belongs to. The second issue is the Invoke-Command. The version always returns 5. I know many of the servers being returned do not have PSVersion 5.
function Get-PSVersion {
(Invoke-Command -Scriptblock {$PSVersionTable.PSVersion}) | Select Major
}
$servers = Get-ADComputer -Filter {(enabled -eq $true) -and (OperatingSystem -like "Windows Server* *")} -Properties * |
ForEach-Object {
$ps = Get-PSVersion
$server = $_
New-Object -TypeName PSObject -Property #{
Name = $server.Name
OS = $server.OperatingSystem
IPAddress = $server.IPv4Address
Location = $server.CanonicalName
PSVersion = $ps.Major
}
}
$servers | Select Name,Location,OS,IPAddress,PSVersion | FT -AutoSize
Ok so starting with the Invoke-Command, You need to tell that cmdlet which server to target, just calling it as you loop over server names will keep calling it on your local computer, so you'll need to use the -computername parameter, and provide your function an argument to pass to invoke-command. Which would look something like this:
function Get-PSVersion($name) {
(Invoke-Command -ComputerName $name -Scriptblock {$PSVersionTable.psversion | Select Major})
}
You'll notice I also moved your select, this isn't strictly necessary but imo it looks cleaner and means slightly less data gets sent over the network. note that this will create an object with a single property called Major, if you want just the version number returned as an integer you'd want to do it like this
function Get-PSVersion($name) {
(Invoke-Command -ComputerName $name -Scriptblock {$PSVersionTable.psversion.Major})
}
You'll need to add an extra loop into the script if you want to target more than one domain, basically you want an array of the domains you wish to target and then loop over that array calling get-adcomputer for each and specifying the domain name for the -server parameter. I've put a simplified example below you can incorporate into your own code.
$arr = #("test.domain","othertest.domain")
foreach($domain in $arr){
Get-ADComputer -Filter * -Server $domain
}
Hope that helps!
Got it to work. Thanks for the assistance.
clear-host
$arr = #("x.local","x.local")
foreach($domain in $arr){
$servers = (Get-ADComputer -Filter {(enabled -eq $true) -and (OperatingSystem -like "Windows Server* *")}-Server $domain -Properties *|Select -First 10)
}
$MasterList = #()
foreach ($server in $servers) {
$MyObj = New-Object PSObject -Property #{
Name = $server.Name
Os = $server.OperatingSystem
Ip = $server.IPv4Address
PSV = Invoke-Command -ComputerName $server.Name -ScriptBlock {$PSVersionTable.psversion}
}
$MasterList += $MyObj
}
$MasterList|Select Name,PSV,OS,IP

PSCustomObject foreach

I've created a custom object and I'm having some trouble with the output of one array.
$i = "computername"
$adsi = [ADSI]"WinNT://$i"
$Object = $adsi.Children | ? {$_.SchemaClassName -eq 'user'} | % {
New-Object -TypeName PSCustomObject -Property #{
ComputerName = $i.toupper() -join ''
UserName = $_.Name -join ''
Groups = ($_.Groups() |Foreach-Object {$_.GetType().InvokeMember("Name", 'GetProperty', $null, $_, $null)}) -join ','
Disabled = Get-WmiObject -ComputerName $i -Class Win32_UserAccount -Filter "LocalAccount='$true'"|Select-Object -expandproperty Disabled
}
}
$object
The problem is with the Disabled array, instead of showing one item per line I'm getting
{False, True, False, False} I know I have to probably add at % somewhere on that line but I'm not sure where.
Anyone have any advice?
What you're seeing makes sense to me - you're creating an array of objects holding ComputerName, UserName, etc. and in Disabled you're getting an array of values because you're querying all local user accounts and getting their disabled status. I suspect what you want is to determine each user in turn is disabled. In which case, you need to extend the Filter on Get-WMIObject a bit to only get a single user.
$i = "computername"
$adsi = [ADSI]"WinNT://$i"
$Object = $adsi.Children | ? {$_.SchemaClassName -eq 'user'} | % {
$UserName = $_.Name -join '';
New-Object -TypeName PSCustomObject -Property #{
ComputerName = $i.toupper() -join ''
UserName = $UserName
Groups = ($_.Groups() |Foreach-Object {$_.GetType().InvokeMember("Name", 'GetProperty', $null, $_, $null)}) -join ','
Disabled = Get-WmiObject -ComputerName $i -Class Win32_UserAccount -Filter "LocalAccount='$true' and name='$UserName'"|Select-Object -expandproperty Disabled
}
}
$object