Trying to create a function that takes objects on the pipeline using the alias property. I'm not sure where this is going wrong.
Example of the process:
function Get-Name
{
Param
(
[Parameter(ValueFromPipelineByPropertyName=$true)]
[alias("givenname")]
[System.String] $FirstName,
[Parameter(ValueFromPipelineByPropertyName=$true)]
[alias("sn")]
[System.String] $LastName
)
write-host "firstName = $FirstName / $($FirstName.GetType().FullName)"
Write-host "LastName = $LastName / $($LastName.GetType().FullName)"
}
If I run this command:
Get-Aduser -filter {sn -eq 'smith'} -properties sn,givenname | Get-Name
the output looks like this:
firstName = / string
LastName = / string
The Function never seems to grab the sn and givenname attributes from the passed in object. What am I missing?
The AD Cmdlets are to blame here
The problem here is that the AD Cmdlets return objects in really non-standard ways. For instance, with any other cmdlet if you take the output of the command and select a non-existing property, you'll get back nothing, like this:
get-date | select Hamster
Hamster
-------
>
See, nothing. Sure, it says Hamster, but there is no actual Object there. This is standard PowerShell behavior.
Now, look at what Get-ADUser does instead:
get-aduser -Filter {sn -eq 'adkison'} | select Hamster
Hamster
-------
{}
It creates a $null! So what will happen with your function is that PowerShell will look for a property of -LastName or -FirstName, get a $null and then stop right there. It sucks!
The best way around this is to swap the parameter names like this, and it will still work:
function Get-Name
{
Param
(
[Parameter(ValueFromPipelineByPropertyName=$true)]
[alias('FirstName')]
[System.String] $givenname,
[Parameter(ValueFromPipelineByPropertyName=$true)]
[alias("sn","lastname")]
[System.String] $Surname
)
write-host "firstName = $givenname / $($givenname.GetType().FullName)"
Write-host "LastName = $SurName / $($SurName.GetType().FullName)"
}
get-aduser -Filter {sn -eq 'adkison'} | Get-Name
firstName = James / System.String
LastName = Adkison / System.String
Want to know more?
Check out this awesome answer from /u/JBSmith on the topic.
From what I've been able to determine, it isn't technically the AD cmdlets that are to blame, but the types in the Microsoft.ActiveDirectory.Management namespace--in this case, ADUser. The properties on ADUser are ultimately all just stored in a private SortedDictionary and fetched through get accessors, which might explain why it doesn't work quite as expected.
As alluded to by Colyn1337 in a previous comment, ADUser doesn't contain a property (or key) named either sn or LastName by default, so you'd need to either include an alias of Surname on your LastName parameter or select sn in your Get-ADUser call:
Get-ADUser -Filter {sn -eq 'Adkison'} -Properties sn | Get-Name
That still won't work, but from there you can just pipe to Select-Object before piping to your function:
Get-ADUser -Filter {sn -eq 'Adkison'} -Properties sn | Select * | Get-Name
Of course, you could also just select the specific properties you need instead of * in Select-Object. I assume this works because it resolves the ADUser dictionary into a PSCustomObject with concrete properties. Once resolved, they will match aliases as well as the actual parameter names.
Related
I have a computers that have assigned to users as managedby, I want to get list in JSON format where hostname is a key, and user attributes are values.
But I stuck to get that in one command :/ and put that in json, so use csv for a while.
I run these 2 commands succesfuly:
Get-ADComputer -Filter * -property managedby | select name, managedby > C:\Windows\Temp\computerowners.csv
Get-ADUser -Filter * -SearchBase 'CN=My User,DC=example,DC=com' -Properties SamAccountName | Format-Table -Property Name, samaccountname, userprincipalname -AutoSize
where search base is managedby value from first one.
I expect to have output like that:
hostname, name, samaccountname, userprincipalname
I try to combine above 2 commands like that:
Get-ADComputer -Filter * -property managedby | foreach {get-aduser -Filter * -SearchBase $managedby -Properties name, samaccountname, userprincipalname} | select name, samaccountname, userprincipalname > C:\Windows\Temp\computerowners.csv
but it want work - as not pickup managedby properly as I understand... any help with saving that in json will be more than welcome.
You didn't define the variable $managedby that you use in the ForEach-Object loop, hence the variable is $null. You need to use the property ManagedBy of the current object in the pipeline ($_.ManagedBy).
With that said, you're making the whole thing way more complicated than it needs to be. PowerShell can do a lot of the heavy lifting for you if you allow it to. Get-ADUser can read from the pipeline, so all you need to do is pass the owner's distinguished name. You also don't need to explicitly specify the properties Name, SamAccountName and UserPrincipalName, because Get-ADUser returns them by default. Plus, since you want CSV output anyway, use Export-Csv instead of the redirection operator.
Get-ADComputer -Filter * -Property managedby |
Select-Object -Expand ManagedBy |
Get-ADUser |
Select-Object Name, SamAccountName, UserPrincipalName |
Export-Csv C:\Windows\Temp\computerowners.csv -NoType
To include the computername in the output adjust the above code as follows:
Get-ADComputer -Filter * -Property managedby |
ForEach-Object {
$computer = $_.Name
if ($_.ManagedBy) { Get-ADUser $_.ManagedBy } else { '' }
} |
Select-Object #{n='ComputerName';e={$computer}}, Name, SamAccountName,
UserPrincipalName |
Export-Csv C:\Windows\Temp\computerowners.csv -NoType
To get a datastructure that can be exported to JSON using the computername as the key for the nested user attributes a different approach would be more elegant, though. Collect all relevant user attributes in a hashtable with the computername as the key:
$computers = #{}
Get-ADComputer -Filter * -Property managedby | ForEach-Object {
$computers[$_.Name] = if ($_.ManagedBy) {
Get-ADUser $_.ManagedBy | Select-Object Name, SamAccountName, UserPrincipalName
} else {
New-Object -Type PSObject -Property #{
Name = ''
SamAccountName = ''
UserPrincipalName = ''
}
}
}
Then create an object from that hashtable and convert it to JSON:
New-Object -Type PSObject -Property $computers | ConvertTo-Json
This should get you pretty far:
Get-ADComputer -Filter * -Property ManagedBy,CN | ForEach-Object {
# only query AD if there actually is a manager
if ($_.ManagedBy) {
$manager = $_.ManagedBy | Get-ADUser
} else {
$manager = $null
}
# return a custom object with 4 properties
[pscustomobject]#{
hostname = $_.CN
name = $manager.Name
samaccountname = $manager.SamAccountName
userprincipalname = $manager.UserPrincipalName
}
}
Note: Any value created in a script block and not explicitly captured in a variable or explicitly discarded via Out-Null automatically becomes a return value of that block. In this case, the ForEach-Object body will emit a series of PSCustomObject instances.
Use the result in any way you like, for example format it as JSON or CSV.
Related reading
How do I return a custom object in Powershell that's formatted as a table?
Jonathan Medd's Blog: PowerShell v3 – Creating Objects With [pscustomobject]
I have this bit of code :
$servers = Import-Csv "sources.csv"
$computername = $servers.server
$ServiceName = $servers.services
sources.csv contains the following..
Server,Services
BRWS40,winrm
BRWS84,winrm
I have then a foreach, and the Write-Host is within that, it output this:
Write-Host "$computername - $ServiceName" -ForegroundColor black -BackgroundColor red
Output from above I get is:
BRWS40 BRWS84 - winrm winrm
Whereas I was wanting to have one computer and service per line.
BRWS40 - winrm
What am I doing wrong?
I amended the code from here.
$servers = Import-Csv "sources.csv" imports the content of sources.csv as a list of custom objects into the variable $servers.
$computername = $servers.server selects the value of the server property of each object into the variable $computername, thus generating a list of computer names.
$ServiceName = $servers.services selects the value of the services property of each object into the variable $ServiceName, thus generating a list of service names.
Note that $array.property will only work in PowerShell v3 and newer, because earlier versions don't automatically unroll the array to get the element properties, but try to access the property of the array object itself. If the array doesn't have such a property, the result will be $null, otherwise it will be the value of the property of the array. Either way it won't be what you want. To make the property expansion work across all PowerShell versions use Select-Object -Expand or echo the property in a ForEach-Object statement:
$computername = $servers | Select-Object -Expand server
$computername = $servers | ForEach-Object { $_.server }
When you put array variables in a string ("$computername - $ServiceName") the array elements are joined by the $OFS character (space by default), so "$computername" becomes BRWS40 BRWS84 and "$ServiceName" becomes winrm winrm.
To get the corresponding service name for each computer you need to process $servers in a loop, for instance:
foreach ($server in $servers) {
Write-Host ('{0} - {1}' -f $server.Server, $server.Services) ...
}
If you don't need a specific output format you could also use one of the Format-* cmdlets, for instance Format-Table:
Import-Csv "sources.csv" | Format-Table -AutoSize
You actually have to loop through your result:
$servers = Import-Csv "sources.csv"
$servers | %{
$computername = $_.server
$ServiceName = $_.services
write-host "$computername - $ServiceName" -foregroundcolor black -backgroundcolor red
}
or use the Format-Table cmdlet:
$servers | Format-Table
I am attempting to grab a SAMAccountName from a SIP address attribute in AD. I keep getting a syntax error that I just can't figure out. I have used similar code to grab a SAMAccountName using the employeeNumber attribute. I have to wonder if the "-" in the attribute name has anything to do with the syntax error.
Import-Csv -Path .\SIP.csv | ForEach-Object {
$sipGet = Get-ADUser -Filter "msRTCSIP-PrimaryUserAddress -eq $($_.'msRTCSIP-PrimaryUserAddress')" |
select -Expand SamAccountName
$_ | select *,#{Name='SamAccountName';Expression={$sipGet}}
} | Export-Csv -Path .\SIP.csv -NoTypeInformation
Any help would be greatly appreciated!
Get-ADUser does not know -eq as a filter. Instead, use plain equal sign, and wrap the string in escaped double quotes.
$sipGet = Get-ADUser -Filter "msRTCSIP-PrimaryUserAddress = \"$($_.'msRTCSIP-PrimaryUserAddress')\"" |
select -Expand SamAccountName
Should do. (Can't test right now, have no access to AD environment)
I am trying to take a filename such as: John_Doe_E_DOB_1/1/46_M(This is the gender)_ID_0000000_IMG_FileName_Date-of-File_1/1/15_Doc-page-1 And create a CSV file to open in Excel with column headers for: Last Name, First Name, MI, ID No, File Name, Date of File along with doc type. Here's my code so far:
Get-ChildItem -Path C:\Users\name\desktop\test -Recurse | ForEach-Object {$_ | add-member -name "Owner" -membertype noteproperty -value (get-acl $_.fullname).owner -passthru} | Sort-Object fullname | Select BaseName,Name,Owner | Export-Csv -Force -NoTypeInformation C:\Users\name\desktop\test\thing.csv
All this is doing is dropping that really long file name in at the top, and then adding the ext at the end in another column. Example:
John_Doe_E_DOB_1/1/46_M(This is the gender)_ID_0000000_IMG_FileName_Date-of-File_1/1/15_Doc-page-1 Would be in column 1 and
John_Doe_E_DOB_1/1/46_M(This is the gender)_ID_0000000_IMG_FileName_Date-of-File_1/1/15_Doc-page-1.txt <----- Would be the only difference in column 2
How can I split this up for over a million files, all different lengths, and sizes, and get it to break up into the categories listed above? All help would be greatly appreciated.
I would replace the Select stage of your pipeline with a call to a filter function like this:
filter GenObj {
$parts = $_.FullName.Split('_')
new-object pscustomobject -property #{
Owner = (get-acl $_.fullname).owner
FirstName = $parts[0]
LastName = $parts[1]
MiddleInitial = $parts[2]
# Fill in the rest
}
}
Get-ChildItem -Path C:\Users\name\desktop\test -Recurse |
Sort-Object fullname |
GenObj |
Export-Csv -Force -NoTypeInformation C:\Users\name\desktop\test\thing.csv
This will create a new custom object with all the properties on it that correspond to the parts of the filename you want to extract.
This string splitting approach may not work depending on how you handle names with no middle initial.
Also be aware that if you are processing a million files, the use of Sort-Object will cause every single FileInfo object (one for every file) to get buffered in memory so the sort can be performed. You may likely run out of memory and the command will fail. I would consider removing Sort-Object in this scenario.
I'm writing a script to find local admins on machines in a specific OU. I've created two functions to preform this task, each function by itself is working fine, but when I combine the two I am not getting any result. Anyone know what I'm doing wrong here?
Function GetCompList{
Get-ADObject -Filter { ObjectClass -eq "computer" } -SearchBase "OU=Resources,DC=Contoso,DC=LOCAL" `
| Select-Object Name
}
Function Admin_Groups{
foreach($i in GetCompList){
$adsi = [ADSI]"WinNT://$i"
$Object = $adsi.Children | ? {$_.SchemaClassName -eq 'user'} | % {
New-Object -TypeName PSCustomObject -Property #{
UserName = $_.Name -join ''
Groups = ($_.Groups() |Foreach-Object {$_.GetType().InvokeMember("Name", 'GetProperty', $null, $_, $null)}) -join ','
}
}
$Object |? {$_.Groups -match "Administrators*"}
}
}
Admin_Groups
Your GetCompList function is returning a collection of objects. You're probably getting this when you run the one function:
Name
------
Comp1
Comp2
Comp3
In the foreach loop of Admin_Groups, you're using the output of GetCompList as an array of primitives - just a list of names, not a bunch of objects. So, you have two options:
Change the select-object name in GetCompList to select-object -expandproperty Name to get a simple array of names
In Admin_Groups, change each reference to $i in the body of the foreach loop to $i.Name. Since you're using it within a string, it's a little ugly to do that.
In this particular example, my preference would be option #1, making that function:
Function GetCompList{
Get-ADObject -Filter { ObjectClass -eq "computer" } -SearchBase "OU=Resources,DC=Contoso,DC=LOCAL" | Select-Object -expandproperty Name
}
I would also suggest that you rename your functions to match the Verb-Noun convention of PowerShell, and use one of the approved verbs from get-verb.
Get-CompList
Get-AdminGroups
Failing that, at least make your function names consistent - either use the _ to separate the words in the names, or don't. Don't mix & match.