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

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}}

Related

Sorting Office 365 user account / mailbox properties

I'm accessing my cloud Office 365 Exchange Server via Powershell. Showing all properties of an account can be done via Get-Mailbox 'username' | Select * (MS reference).
On most systems those properties are already sorted (e.g. Get-ADUser 'username' -Properties *). Is it possible to sort the Get-Mailbox output? I thought Get-Mailbox 'username' | Select * | Sort-Object would do the trick but it didn't make any difference (I guess a property doesn't constitute an object). What's the right command to sort the properties for a single user?
Note: Sorting properties of multiple accounts works fine e.g. Get-Mailbox -filter * | select Name, DisplayName | Sort-Object Displayname
Update:
I managed to get a sorted properties list using
(Get-Mailbox 'mailboxname' | select *).PSObject.properties | ForEach-Object {$_.Name} | Sort-Object Name
it gets me the following output:
AcceptMessagesOnlyFrom
AccountDisabled
AddressBookPolicy
ArchiveWarningQuota
...
$_.Name gives me the values but so far I couldn't combine both into one list, I'm trying to get s.th. like this:
AcceptMessagesOnlyFrom = {}
AccountDisabled = False
AddressBookPolicy =
ArchiveWarningQuota = 45 GB (48,318,382,080 bytes)
...
I'm not completely sure this is what you are after, let me know if I'm wrong. As in my comment, Sort-Object can handle sorting a list or an object[] by one or more of it's properties; but sorting one single object by it's properties, say alphabetically, would require a combination of accessing the object's properties with .PSObject.Properties.Name and then sorting this list with Sort-Object. And after that we can use Select-Object with this sorted list to display the object as we want.
Using the object below as an example as I have no idea how of the type Microsoft.Exchange.Data.Directory.Management.Mailbox looks.
$mailbox = [pscustomobject]#{
DisplayName = 'someuser'
UserPrincipalName = 'someuser#somedomain.com'
Mail = 'someuser#somedomain.com'
IsLicensed = $true
}
$properties = $mailbox.PSObject.Properties.Name | Sort-Object
$mailbox | Select-Object $properties
As you can see, object's properties are now sorted alphabetically:
DisplayName IsLicensed Mail UserPrincipalName
----------- ---------- ---- -----------------
someuser True someuser#somedomain.com someuser#somedomain.com
By looking at your edit, seems like you are looking for a one-liner, so this is how it could look:
Get-Mailbox 'mailboxname' | ForEach-Object {
$_ | Select-Object ($_.PSObject.Properties.Name | Sort-Object)
}

displaying information from two objects in powershell

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.

CSV export is missing data in one of the columns

I have a csv file that this script works fine on.
$CCure = Import-csv C:\Scripts\file1.csv
ForEach ($user in $CCure) {
[string]$1Name = $User.FirstName
[string]$2Name = $User.LastName
[string]$GivenName = $1Name.Split(" ")[0]
[string]$SN = $2Name.Split(",")[0]
[string]$ID = $User.ObjectID
[string]$EmpID = $User.Int5 |
Select #{Name="First Name";Expression={$GivenName}}, #{Name="Last Name";Expression={$SN}}, #{Name="CCure ObjectID";Expression={$ID}}, #{Name="CCure Int5 Row";Expression={$EmpID}} |
Export-csv C:\Scripts\CCure\CCure-Names-Test.csv -Append -notypeinformation
}
However, when I try a similar script going out to AD and want to use RegEx, things don't work. It seems to hang or when it does run, the SurName is missing. Why doesn't the second one work? I must be missing something simple?
$Users = Get-ADUser -LDAPFilter "(&(&(objectCategory=person)(objectClass=user)(!userAccountControl:1.2.840.113556.1.4.803:=2))" -properties samaccountname,givenname,sn
ForEach ($User in $Users) {
[string]$1Name = $User.GivenName
[string]$2Name = $User.SN
[string]$GivenName = $1Name.Split(" ")[0]
[string]$SN = $2Name.Split(",")[0] |
Select #{Name="First Name";Expression={$GivenName}}, #{Name="Last Name";Expression={$SN}},#{Name="SID";Expression={$User.samaccountname}}| Export-Csv C:\scripts\ccure\AD-Active-Names3.csv -NoTypeInformation -append
}
In your second example $sn would be null. This is because of the trailing pipe character that you have. This issue is also present in your first code block for $EmpID. The last command Export-CSV returns nothing to the output stream so the variable in both cases would be $null.
You are taking the result of $2Name.Split(",")[0] and sending that into the pipeline which is then left unused for Select-Object.
So the simple answer is remove the pipeline character and those two lines will now work separately.
[string]$SN = $2Name.Split(",")[0]
Select #{Name="First Name";Expression={$GivenName}}, #{Name="Last Name";Expression={$SN}},#{Name="SID";Expression={$User.samaccountname}}| Export-Csv C:\scripts\ccure\AD-Active-Names3.csv -NoTypeInformation -append
Also consider making your own object if you are changing property names and values that much. This should accomplish the same thing, be easier to read and take advantage of the pipeline.
$Users = Get-ADUser -LDAPFilter "(&(&(objectCategory=person)(objectClass=user)(!userAccountControl:1.2.840.113556.1.4.803:=2))"
$Users | ForEach-Object {
$props = #{
"First Name" = ($_.GivenName).Split(" ")[0]
"Last Name" = ($_.SurName).Split(",")[0]
"SID" = $_.samaccountname
}
New-Object -TypeName psobject -Property $props
} | Select-Object "First Name","Last Name",SID | Export-Csv C:\scripts\ccure\AD-Active-Names3.csv -NoTypeInformation
You will note that I used $_.SurName as the property names used since PowerShell AD Objects do not directly match their LDAP attribute names. If you tried to access $_.SN you would get a null value. GivenName,Surname and SID are part of the value set returned by default so you don't need to request them.
Looking at about_ActiveDirectory_ObjectModel you will see under AdUser:
Surname - A property of type System.String, derived from the directory attribute: sn
You will still get errors with this if Surname or GivenName are not populated. Accounting for that would be a simple if statement if that is going to be an issue.

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

Powershell pipeline - Retrieve outputs from first cmdlet?

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.