Powershell Function Variables - function

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.

Related

Serialize JSON from Powershell in a specific fashion

So I have this script that goes out and finds all the software versions installed on machines and lets people know what software and when it was installed across several VMs.
I want to put this on a Dashboard provider we use but they have a specific format in which to use it.
it does produce a valid JSON however I just found out it's not in the format the company wishes.
which would be:
{"table": [["header1", "header2"], ["row1column1", "row1column2"], ["row2column1", "row2column2"]]}
My first thought would be to produce a header row as a beginning variable and then individual variables for each component but that feels very tedious and laborious to create variables for each individual row of data (Date, Name of Software, etc). then at the end combine them into 1 and convert to json
My script is this:
[CmdletBinding()]
Param (
[Parameter(ValueFromPipeline = $true,
ValueFromPipelinebyPropertyName = $true)]
[Alias("Servers")]
[string[]]$Name = (Get-Content "c:\utils\servers.txt")
)
Begin {
}
Process {
$AllComputers = #()
#Gather all computer names before processing
ForEach ($Computer in $Name) {
$AllComputers += $Computer
}
}
End {
ForEach ($Computer in $AllComputers) {
write-output "Checking $computer"
if ($computer -like "*x86*") {
$data = Invoke-Command -cn $computer -ScriptBlock {Get-ItemProperty HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\* | Select-Object #{Label = "ServerName"; Expression = {$env:computername}}, DisplayName, Publisher, DisplayVersion, InstallDate | Where-object { $_.Publisher -match "Foobar" }}
$jsondata += $data
}
else {
$data = Invoke-Command -cn $computer -ScriptBlock { Get-ItemProperty HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\* | Select-Object #{Label = "ServerName"; Expression = {$env:computername}}, DisplayName, Publisher, DisplayVersion, InstallDate | Where-object { $_.Publisher -match "foobar" } }
$jsondata += $data
}
}
$jsondata | ConvertTo-Json -depth 100 | Out-File "\\servername\C$\Utils\InstalledApps.json"
}
From the sample output format provided I would conclude that you are looking for an array of array. There is a "bug" using ConvertTo-Json when trying to do this but since we need it inside a table object anyway. I will show an example using your code but just on my local computer. Integrating this into your code should not be an issue.
# gather the results
$results = Get-ItemProperty HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\* | Where-object { $_.Publisher -match "The" } | Select-Object #{Label = "ServerName"; Expression = {$env:computername}}, DisplayName, Publisher, DisplayVersion, InstallDate
# Prepare an array of arrays for the output.
$outputToBeConverted = #()
# build the header
$header = ($results | Get-Member -MemberType NoteProperty).Name
$outputToBeConverted += ,$header
# Add the rows
Foreach($item in $results){
# Create a string array by calling each property individually
$outputToBeConverted += ,[string[]]($header | ForEach-Object{$item."$_"})
}
[pscustomobject]#{table=$outputToBeConverted} | ConvertTo-Json -Depth 5
Basically it is making a jagged array of arrays where the first member is your "header" and each row is built manually from the items in the $results collection.
You will see the unary operator , used above. That is done to prevent PowerShell from unrolling the array. Without that you could end up with one long array in the output.

How to get via Powershell AD computer owner attributes like email and account name?

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]

Reading from CSV produces duplicate entries in variable

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

PowerShell function ValueFromPipelineByPropertyName not using alias parameter name

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.

powershell ip address csv file

I am trying to dump the contents of only the live adapters to a csv file, for later importing.
The issue was the usage of $_. below.
$colNicConfigs = Get-WMIObject Win32_NetworkAdapterConfiguration | where { $_.IPEnabled -eq "TRUE" }
#loop over each adapter
foreach ($objNicConfig in $colNicConfigs)
{
$objnic=Get-WMIObject Win32_NetworkAdapter | where {$_.deviceID -eq "$objNicConfig.Index" }
#$strname=$objnicconfig.description.split(":")[0]
#replace strname above when testing against actual server since no dot1q defined on my wks
$strname="MGMT:Something"
$connid=$_.NetworkConnectionID
$ipaddr=$_.IPAddress(0)
$ipsm=$_.IPSubnet(0)
$dg=$_.DefaultIPGateway
}
# create dictionary entries
$report = #()
$report += New-Object psobject -Property #{Name=$strname;ConnID=$connid;IP=$ipaddr;SM=$ipsm;DG=$dg}
$report | export-csv .\nic.csv
Your initial issues are the use of "$underscore" within your foreach loop. If you want to reference properties of the $objNicConfig you will use that in place of the "$underscore". So instead of $connid=$_.networkConnectionID you would use $connid=$objNicConfig.networkConnectionID
Also IpAddress and IPSubnet are not methods they are properties, so dropping the (0) will return the write info. If your NIC has multiple IPs I cannot attest to how this will display as my machine does not, that I'm testing on.
Other things I see is that you will need to nest another foreach loop in there in order to reference both WMI namespaces...so something like:
$colNicConfigs = Get-WMIObject Win32_NetworkAdapterConfiguration | where { $_.IPEnabled -eq "TRUE" }
foreach ($objNicConfig in $colNicConfigs)
{
foreach($objnic in (gwmi win32_networkadapter | where {$_.DeviceID -eq $objNicConfig.Index}))
{
$strName = "MGMT:Something"
$objNicConfig.NetworkConnectionID
$objNicConfig.IpAddress
$objNic.IPSubnet
$objNicConfig.DefaultIPGateway
}
}
The above code is what I used to return info on the NICs of my computer.
Now with the "dictionary entries" section. You will not be able to reference the variables within your foreach loop in the manner of adding a psobject. You are only going to capture the last one found within the foreach loop code. If you want to first collect the information in your foreach loop and then use it later down in your script I would suggest looking at hash tables for this.