Powershell pipeline - Retrieve outputs from first cmdlet? - powershell

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.

Related

powershell how do I Create/format a dynamic list from a group membership to use in a for each loop

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}

PowerShell referencing object in a pipeline

I'm struggling with passing objects in a pipeline.
I have been going round the problem converting them to strings, but that cannot be the most efficient way of doing things.
$mapi = (Get-CASMailbox -Identity $user | fl mapiEnabled | Out-String ).Split(':')[-1]
if ($mapi -match "True") {
Set-CASMailbox -Identity $User -MAPIEnabled $false
}
I really want to directly access the bool returned instead of converting it to string
Similarly, I have been using below to do a for loop:
$groups = (Get-DistributionGroup | fl name | Out-String -Stream ).Replace("Name : ", "")
foreach ($group in $groups) {
echo $group
}
Both examples are from Exchange Online, below one more universal:
if (((Get-NetIPInterface -InterfaceAlias $adapters -AddressFamily Ipv4 | fl dhcp | Out-String -Stream ).Trim() -ne "").Replace("Dhcp : ","") -match "Disabled") {
echo disabled
}
I just wanted to take a second to see if I can help you understand what is happening in the pipeline and why #mathiasR.Jessen and #AdminOfThings comments will help you.
$mapi = (Get-CASMailbox -Identity $user | fl mapiEnabled | Out-String ).Split(':')[-1]
Breaking down that this line of code does:
Get-CASMailbox is going to return an object with multiple properties. Format-List (fl) is still going to return an object, but now it has been formatted so it's less malleable. Out-String is going to transform that formatted list into a single string. Putting those commands in parentheses runs them and allows you to execute a method on the resulting string object.
Using the same concept, we can use the parenthesis to execute the Get-CASMailbox command and get the singular property you are looking for:
$mapi = (Get-CASMailbox -Identity $user).mapiEnabled
Now we have set $mapi to the value of the mapiEnabled property returned by the command.
Hope this helps!

Combining Powershell cmdlets issue

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

Remove #{ColumnName from Powershell results

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.

In PowerShell, how can I combine the results of two commands that have a 1-to-1 relashionship?

This particular example is Get-User and Get-Mailbox (Exchange 2010). Get-User returns some of the columns I need, and Get-Mailbox some others. I am having difficulty figuring out how I can combine the results of the two into a single table with the results from both.
Get-User -Filter "..." | Get-Mailbox -Filter "..."
How do I take the results of a command similar to the above and turn it into results similar to below?
FirstName LastName Alias CustomAttribute1
--------- -------- ------ ----------------
Bob Smith bsmith Example
Johnny NoMail
Adam Blye ablye Has a Mailbox
Note that FirstName and LastName are not returned by Get-Mailbox, and conversely Alias and CustomAttributes are not returned from Get-User. Not every user has a mailbox, so sometimes a portion of the columns would be null. But I'm having a devil of a time figuring out the best way to return a combined table like this.
Get the users, save each user in a variable, get the mailbox for each user and then create a new object with properties from both variables
Get-User -Filter ... | Foreach-Object{
$user = $_
$mbx = Get-Mailbox $user
New-Object -TypeName PSObject -Property #{
FirstName = $user.FirstName
LastName = $user.LastName
Alias = $mbx.Alias
CustomAttribute1 = $mbx.CustomAttribute1
}
}
I make a custom object, which may be overkill, but it's the simplest way I've found.
Here's some sample code to play with. Let me know if it generates any trouble or additional questions:
$outputCollection = #()
$users = Get-User -Filter "..."
$mailboxes = Get-Mailbox -Filter "..."
$users | Foreach-Object {
#Associate objects
$userObject = $_
$mailboxObject = $mailboxes | Where-Object {$_.Name -eq $userObject.Name}
#Make a combined object
$outputObject = "" | Select Name, UserAttribute, MailboxAttribute
$outputObject.Name = $userObject.Name
$outputObject.UserAttribute = $userObject.UserAttribute
$outputObject.MailboxAttribute = $mailboxObject.MailboxAttribute
#Add the object to the collection
$outputCollection += $outputObject
}
$outputCollection
Another option that should work is called calculated properties:
Get-User -Filter "..." | Select Name, UserAttribute, #{Name="OtherAttribute"; Expression={(Get-Mailbox $_.Name).MailboxAttribute}}
...note that this will run a new Get-Mailbox command for each entry, potentially increasing execution time
Thank you guys. I spent a hell of a lot of time trying to figure out my own issue and your code helped me get it right. I needed to find all the calendars were the default account was set to none. Below is what I needed up using.
Get-Mailbox | ForEach-Object{
$user = $_
$calPerms = Get-MailboxFolderPermission $user":\calendar" -User Default | Where-Object {$_.AccessRights -eq "none"}
New-Object -TypeName PSObject -Property #{
Name = $user
Permissions = $calPerms.AccessRights
Users = $calPerms.user
}
}
One liner to get FirstName, LastName, Alias and CustomAttribute1:
Get-User | Select Firstname, Lastname, #{Name="Alias"; Expression={(Get-Mailbox $_.Name).Alias}}, #{Name="CustomAttribute1"; Expression={(Get-Mailbox $_.Name).CustomAttribute1}}