I'm relatively new to PowerShell scripting and have mainly been cobbling together different scripts and cmdlets from Googling what I'm trying to do. One problem that I'm unable to Google, or search for on StackExchange, because of the special characters is having all my results come out as #{ColumnName=ColumnData}.
Here's an example script I found somewhere for pulling all the members of an AD group.
$Groups = Get-ADGroup -Filter {Name -like "!GroupName"}
$path = $groups
$myCol = #()
ForEach ($Group in $Groups)
{
$Members = #(Get-ADGroupMember "$Group")
ForEach ($Member in $Members){
try{
$user = get-aduser -identity $member -properties displayname
$MyObject = New-Object PSObject -Property #{
Displayname = $user.Displayname
}
$mycol += $MyObject}
catch {}}
}
Write-Host $MyCol | FL
I'm pretty sure there are better ways to get the members of an AD group but that's not the issue at the moment. The problem is all the data comes out like #{Displayname=Lawrence, Kimberly} and this happens with many of the scripts I've thrown together.
Any ideas on how to write scripts properly so I can just get DisplayName = Lawrence, Kimberly?
I agree your code is complex and it does not need to be. A couple of things that are important to mention is why your output looks the way it does.
You are creating an array of PSCustomObjects which function similar to hash-tables. In the end of your processing you have an array of objects with the property DisplayName. Since you are using Write-Host to display the the contents of $myCol it needs to cast it a string array in order to display it. You would see similar output if you just typed [string[]]$myCol. I should hope that you are doing something else in the processing as like the other answers show you have a very complicated way of getting what you are looking for.
Without beating the horse about those other solutions I will suggest some minor changes to yours as it stands. Your title and last sentence contradict what you are looking for since you want to remove the ColumnName in the title and the last sentence you are looking for output like DisplayName = Name.
Changing the last line will address both of these. If you just want the displaynames on there own
$myCols | Select-Object -ExpandProperty DisplayName
Lawrence, Kimberly
and if you actually want the other format you could do this
$myCols | ForEach-Object{
"DisplayName = $($_.DisplayName)"
}
Again, like the other answers, I stress that your code could use simple overhaul. If you needed to understand why it was working the way it way I hope I helped a little.
This actually has nothing to do with Active Directory; You are creating custom objects, then outputting them directly, and by default that is how PowerShell will format the output if you use Write-Host (which is intended to customize output). If you would like to be more specific about what your output should look like, we can help, but here is an example that outputs the results as just a list of strings, just using Write-Output:
$myCol = #()
$MyObject = New-Object PSObject -Property #{ DisplayName = "Lawrence, Kimberly" }
$myCol += $MyObject
$MyObject = New-Object PSObject -Property #{ DisplayName = "Hello, World" }
$myCol += $MyObject
# The default will show at-symbols, etc: Write-Host $MyCol | FL
Write-Output $myCol
The output will be:
DisplayName
-----------
Lawrence, Kimberly
Hello, World
Try this:
Get-ADGroup -Filter "Name -like '!GroupName'" |
Get-ADGroupMember |
Get-ADUser -Properties DisplayName |
Select-Object DisplayName
When I ran your code, I also got the hashes, even without special characters. Your code is way too unnecessarily complex.
Related
bit of a noob question.
I have the following cmd which grabs the server members of a group which I can copy into a text list. however as the group changes I need to modify the text list manually.
Get-AdGroupMember -identity "Reboot 7pm" | Sort-Object | select name
when I have that output in a text list, the following works fine.
$listpath = "C:\Scripts\servers.txt"
[System.Collections.ArrayList]$list = #(Get-content $listpath)
foreach($ComputerName in $list)
{
Get-Uptime -ComputerName $ComputerName
I want to know if it is possible to use a variable that I can use again in a for each loop. I've tried to do so, however the format of the list is not the same when is goes into a variable, thus the function (get-uptime) against the server doesn't work, anyone know what I can do to format the output so I only get the server name?
EG.
$WSUS_7PM = Get-AdGroupMember -identity "Reboot 7pm" | Sort-Object | select name
PS C:\Windows\system32> $WSUS_7PM
name
----
AXXXXX003
BXXXXX005
CXXXXX006
DXXXXX007
PS C:\Windows\system32> foreach($Name in $WSUS_7PM) {Write-Host $Name}
#{name=AXXXXX003}
#{name=BXXXXX005}
#{name=CXXXXX006}
#{name=DXXXXX007}
so when I run the same cmds as above modified with the variable instead of the text list, I get the following as the server name is obviously incorrect.
$listpath = $WSUS_7PM
[System.Collections.ArrayList]$list = #(Get-content $WSUS_7PM)
foreach($ComputerName in $list)
{
Get-Uptime -ComputerName $ComputerName
WARNING: Unable to connect to #{name=AXXXXX003}
WARNING: Unable to connect to #{name=BXXXXX005}
I hope that makes sense to someone, appreciate the help in understanding what the difference is in the object output.
Thanks
Alzoo
When you use Select-Object name you are creating a list of objects with a name property. You can either expand it ahead of time
$WSUS_7PM = Get-AdGroupMember -identity "Reboot 7pm" | Sort-Object | Select-Object -ExpandProperty name
or reference the name property later
foreach($Name in $WSUS_7PM.name) {Write-Host $Name}
I'd like to display information from skype and from AD next to eachother. (I'm asking this as a specific question, but I've stumbled against this more often).
the simplified version would be this:
get-csuser | ForEach-Object { $_.displayname,(Get-ADUser -Identity $_.samaccountname -Properties officephone).officephone } | ft
This would show me the displayname from skype and the phone number from AD. But it will show it as new rows, while I would like to have it in one handy table.
What's the best way to achieve this?
Thank you
I think you are looking for "calculated properties" in Select-Object. name is your label and expression is a script-block. Both are wrapped in a hash-table.
#{ Name = ''; Expression = {}}
So, for your code, it can be like this(didn't check for syntax) :
get-csuser | ForEach-Object { $_.displayname, #{ n= 'officePhone'; e = { (Get-ADUser -Identity $_.samaccountname -Properties officephone).officephone } }
Note : we can use short-hands for name and expression.
Link : Further Examples
Maybe this helps you.
$list = foreach ($process in (Get-Process)) {
[PSCustomObject]#{
ProcessID = $process.Id
Name = $process.Name
}
}
$list | Format-Table
Of course, you adjust the loop for the objects you enter the information you want to get, but generally this may help.
I've pieced together a script (sorry can't remember the source), that returns multiple attributes using two cmdlets, (Get-user & Get-mailboxstatistics). The code works as expected if I specify an individual user but when using a wildcard to return all users it only returns the attributes from Get-user, and I don't know why.
Any help in resolving this is appreciated.
$outputCollection = #()
$users = Get-User -identity *
$mailboxes = Get-Mailboxstatistics -identity *
$users | Foreach-Object {
#Associate objects
$userObject = $_
$mailboxObject = $mailboxes
$emailObject = $mail
#Make a combined object
$outputObject = "" | Select FirstName,Lastname,sAMAccountName,windowsemailaddress,ItemCount,Totalitemsize,TotalDeletedItemSize,DatabaseName,ServerName,LastLogonTime,LastLogoffTime
$outputObject.FirstName = $userObject.FirstName
$outputObject.Lastname = $userObject.Lastname
$outputObject.sAMAccountName = $userObject.sAMAccountName
$outputObject.windowsemailaddress = $userObject.windowsemailaddress
$outputObject.itemcount = $mailboxObject.itemcount
$outputObject.Totalitemsize = $MailboxObject.Totalitemsize
$outputObject.TotalDeletedItemSize = $MailboxObject.TotalDeletedItemSize
$outputObject.DatabaseNAme = $mailboxObject.DatabaseName
$outputObject.ServerName = $mailboxObject.ServerName
$outputObject.lastlogontime = $mailboxObject.lastlogontime
$outputObject.lastlogofftime = $mailboxObject.lastlogofftime
#Add the object to the collection
$outputCollection += $outputObject
}
$outputCollection
To help you understand I changed as little as possible. For starters I would remove the $mailboxes = Get-Mailboxstatistics -identity * line. Then, for simplicity sake, update the line below
$mailboxObject = Get-Mailboxstatistics -identity $userObject.UserPrincipalName
You need to get the statistics for the one mailbox in each loop pass. Changing how you populate the $mailboxObject should accomplish that without the need to change anything else.
You can possibly remove $emailObject = $mail since you don't appear to be using it anywhere.
FYI This is not tested but should work. This also assumes that the user actually has an exchange mailbox. If not there will be null values in the output.
About efficiency
I wanted to try and make this simpler for you to understand. However note that what Bacon Bits was trying to tell you about not running Get-Mailboxstatistics every time is true. My solution should still work though.
The basic problem is that there's no correlation between $users and $mailboxes.
For example: Why would $mailboxObject = $mailboxes automatically pick the right user's mailbox? The answer is that it wouldn't. It would return everything that was in $mailboxes. So $mailboxObject.FirstName doesn't mean anything, because $mailboxObject is an array, just like $mailboxes was. You could say ``$mailboxObject[0].FirstName`, but that wouldn't correlate correctly.
You need a key field that exists in both objects that you can lookup with a $mailboxObject = $mailboxes | Where-Object { $_.SomeKeyField = $userObject.SomeKeyField } statement.
Alternately, you can wait to run Get-MailboxStatistics until you're inside your loop, but that will significantly increase the amount of traffic to your Exchange system.
Also, bear in mind that all the above assumes that a User exists for every MailboxStatistic. If that's not the case, you'll have to do even more work if you want all of both in the results.
You could try this!
As mentioned by Bacon, either $users or $mailboxes should be filtered with a relationship between the two; Just like a Foreign Key.
**Always use Hash-Table when adding Key-Value pairs to have more options to manipulate and easy understanding
# DECLARE HASHTABLE
$outputCollection = #{}
$users = Get-User -identity *
$mailboxes = Get-Mailboxstatistics -identity $userObject.UserPrincipalName
$users | Foreach-Object {
#Associate objects
$userCollection = $_
$mailboxCollection = $mailboxes | Where-Object { $_.SomeKeyField = $userCollection.SomeKeyField }
# ADD ELEMENTS TO HASH TABLE
# Add elements from userCollection
$outputCollection.add("FirstName",$userCollection.FirstName);
$outputCollection.add("Lastname",$userCollection.Lastname);
$outputCollection.add("sAMAccountName",$userCollection.sAMAccountName);
$outputCollection.add("windowsemailaddress",$userCollection.windowsemailaddress);
# Add elements from mailboxCollection
$outputCollection.add("itemcount",$mailboxCollection.itemcount);
$outputCollection.add("Totalitemsize",$mailboxCollection.Totalitemsize);
$outputCollection.add("TotalDeletedItemSize",$mailboxCollection.TotalDeletedItemSize);
$outputCollection.add("DatabaseNAme",$mailboxCollection.DatabaseNAme);
$outputCollection.add("ServerName",$mailboxCollection.ServerName);
$outputCollection.add("lastlogontime",$mailboxCollection.lastlogontime);
$outputCollection.add("lastlogofftime",$mailboxCollection.lastlogofftime);
}
$outputCollection
By using Hash Table you could also be using different name for the Keys, Example you could do this,
outputCollection.add("Last Logoff Time",$mailboxCollection.lastlogofftime);
#Instead of
outputCollection.add("lastlogofftime",$mailboxCollection.lastlogofftime);
Hope this helps!!
Thanks for all the help. I finally managed to sort this using the following code.
Get-MailboxServer | Get-Mailbox -resultsize 1 | foreach-object {$email = $_.primarysmtpaddress; $_ | Get-MailboxStatistics | Sort #{expression= "totalitemsize";descending=$true}}| select DisplayName, #{expression={$_.totalitemsize.value.ToMB()};label=”Size(MB)”}, #{expression={$_.TotalDeletedItemSize.value.ToMB()};label=”Deleted Size(MB)”}, ServerName, DatabaseName, itemcount, lastlogontime, lastloggedonuseraccount, #{Name="EmailAddress";expression={$email}}| Export-csv c:\Report.csv -notypeinformation
Im pulling out information from AD to output alot of information to be handled at a later point.
I need to export this to a csv or something so i can have a rollback ( in effect import file so i can handle each object like i can with the hashtable below. Especially that inside $_.MemberOf).
The hashtable im struggling with handling is:
$logging1 = #{
MemberOf="$users.MemberOf"
OriginalOU="$Ou.DistinguishedName"
DisabledWhen="$descriptionDisabled"
}
$logging = #{$users.SamAccountname = $logging1}
what the rest of the script does is(that isnt pasted ) is:
iterate through a bunch of ou's. Getting users last logged in -90 days ago or more then passing them to the HashTable in question which i like to append to a file to be imported at a later time.
I have been googling for hours without getting somewhere
here is the complete script:
import-module ActiveDirectory
$descriptionDisabled = get-date -Format yyyyMMdd
$Loggdir = "C:\temp"
$array = #{}
$loggname = get-date -f yyyyMMdd
$90days = ((get-date).AddDays(-90))
$searchBase = 'OU=someou4,OU=someou3,OU=someou2,OU=someou1,DC=name,DC=NO'
$ExclusionList = Someexlutions
$OUlist = Get-ADOrganizationalUnit -SearchBase $searchBase
foreach ($Ou in $OUlist)
{
$ExpiredADusers = get-aduser -Filter {(LastlogonDate -le $90days)
-and (Enabled -eq $True)}
-SearchBase $ou.distinguishedname
-Properties *
#Get information about every user and their groupmembership
foreach ($Users in $ExpiredADusers)
{
$users = get-aduser -Identity $users.SamAccountName
-Properties *
| Select-Object -Property SamAccountName, MemberOf,
DistinguishedName, ObjectGUID
$logging = #{$person.SamAccountName = #{
MemberOf=$person.MemberOf;
OriginalOU=$Ou.DistinguishedName;
DisabledWhen="$descriptionDisabled"}}
$Array += $Logging
}
}
$array | export-clixml -path somepath -noclobber
EDIT SOLVED
Solved problem and original script in question has been updated to handle Nested hashtables
Original problem
I think you can understand what im trying to do.
Im making a rollback file incase needed.
So the exported file need to be easy to import. when using hashtables you can use . notations and each objects under memberof is treated as an object
What the rollback needs is so i can iterate through the imported info to move the user back to its original OU placement and restore membership
I think EBGreen really hit the nail on the head here. You need an array of custom objects, and then you can just export it to a CSV like you want to. It's a really minor change in code too.
$Array = #()
$ForEach($User in $Users){
$logging1 = New-Object PSObject -Property #{
MemberOf=$user.MemberOf
OriginalOU=$Ou.DistinguishedName
DisabledWhen=$descriptionDisabled
}
$Array += $Logging1
}
Edit: Hm, so you want to be able to export and import full objects. You don't want a CSV then because you have nested arrays, and a CSV is not designed to handle that for export and import. You need XML, so, as mentioned above, you need to use Export-Clixml and Import-Clixml since XML can handle nested arrays. Just pipe the array to it once the array has all your data and you should be all set.
Edit2: The hashtables within hashtables issue... Ok, so we had it as MemberOf="$user.MemberOf" and that's the issue. It is converting it to a string, so it is expanding the entire $User variable, and tacking .MemberOf to the end of it. We don't really want to do it in this case, but if you want to access a property of an object from within doublequotes you need to put $() around it. For example if you wanted to include the user's distinguishedname as a part of human friendly output you could do something like:
Write-Output "$($Users.Name)'s distinguished name is: $($users.distinguishedname)"
Which would output something like:
TMTech's distinguished name is: CN=TMTech,OU=Awesome,OU=Administrators,DC=Digital,DC=Ghost,DC=net
I am trying a few things in Powershell and what I don't manage to achieve is the following (in Exchange):
Get-User | Get-MailboxStatistics
But in the output I would like some fields/outputs from the "Get-User" cmdlet and some fields/outputs from the "Get-MailboxStatistics" cmdlet.
If anyone has an answer, I have searched the web but with no success as I've had difficulties explaining it in a few words.
Thanks in advance for your help.
Start with the execution of one cmdlet, pipe the results to Foreach-Object and then save a reference to the current object ($user), now execute the second command and save it in a variable as well. Create new object with properties from both objects.
You also need to filter users that have mailboxes, use the RecipientTypeDetails parameter.
$users = Get-User -RecipientTypeDetails UserMailox
$users | Foreach-Object{
$user = $_
$stats = Get-MailboxStatistics $user
New-Object -TypeName PSObject -Property #{
FirstName = $user.FirstName
LastName = $user.LastName
MailboxSize = $stats.TotalItemSize
ItemCount = $stats.ItemCount
}
}
I don't know if it is the best or optimal solution, but you certainly do it by saving actual user to variable in foreach:
$users = Get-User
$users | % { $user = $_; Get-MailboxStatistics $_ | %
{
"User name:{0} - some mailbox statistics: {1}" -f $user.SomePropertyOfUser, $_.SomePropertyOfMailbox
}
}
The first step (saving users into separate variable) is required only when working with Exchange cmdlets - as mentioned here, you cannot nest Exchange cmdlets in foreach...
This error is caused when executing the Exchange cmdlets through PowerShell remoting, which do not support more than one pipeline running at the same time. You may see this error when you pipe the output from a cmdlet to foreach-object, which then runs another cmdlet within its scriptblock.
$users = Get-User -RecipientTypeDetails UserMailbox
$users | Foreach-Object{ $user = $_; $stats = Get-MailboxStatistics $user.DistinguishedName; New-Object -TypeName PSObject -Property #{FirstName = $user.FirstName; LastName = $user.LastName;MailboxSize = $stats.TotalItemSize;ItemCount = $stats.ItemCount }}
I've had to add a specific field in input of Get-MailboxStatistics because remotely, I was having:
The following Error happen when opening the remote Runspace: System.Management.Automation.RemoteException: Cannot process argument transformation on parameter 'Identity'. Cannot convert the "gsx-ms.com/Users/userName1" value of type "Deserialized.Microsoft.Exchange.Data.Directory.Management.User" to type "Microsoft.Exchange.Configuration.Tasks.GeneralMailboxOrMailUserIdParameter".
Anyway, thank you both #Jumbo and #Shay-levy
Get-ADUser -identity ADACCOUNT | Select-object #{Name="Identity";Expression={$_.SamAccountName}} | Get-MailboxStatistics
For some reason the Identity parameter doesn't take pipelne input by value, only by property name. So in order to get it to work you can change the name of piped in data to match the parameter name of Identity. Then Get-MailboxStatistics finally knows how to treat the data your feeding it via the pipeline.