I have a small script to get all user mobile devices info from exchange 2013 server.
Get-Mailbox -ResultSize Unlimited |
ForEach {Get-MobileDeviceStatistics -Mailbox:$_.Identity} |
Select-Object #{label="User" ; expression={$_.Identity}},DeviceOS, lastsuccesssync
I just want to get an exact user name instead of a path in AD. How can I do it in expression={?}
Here is another script to do it, it gives me the user name, but all devices belongs to user are not in separated lines, they all in one line...
$EASMailboxes = Get-CASMailbox -Filter {HasActiveSyncDevicePartnership -eq $True -and DisplayName -notlike "CAS_{*"} | Get-Mailbox
$EASMailboxes | Select-Object DisplayName, PrimarySMTPAddress, #{Name="Mobile Devices";Expression={(Get-MobileDeviceStatistics -Mailbox $_.Identity).DeviceOS}} |
Out-GridView
I don't have the environment to test this but is this not what you are looking for ?
Get-Mailbox -ResultSize Unlimited | ForEach {
$user = $_.SamAccountName
Get-MobileDeviceStatistics -Mailbox:$_.Identity |
Select-Object #{label="User" ; expression={$user}},DeviceOS, lastsuccesssync
}
That should output the user for every device they own on its own line. You could then easily export this to Export-CSV or some such thing that way.
We save the $user so it is available later in the pipe. Could also have used Add-Member but the result would have been the same.
If you have the identity field, which looks like this
domain.com/Users/OU/UserName/ExchangeActiveSyncDevices/iPhone
Then to split on the / and get the third result, you simply request:
$_.Identity.Split("/")[3]
>UserName
PowerShell begins indexing with number zero, so to request the fourth entry in the list, we request index number 3.
Update
OP mentioned that the OU level might vary, meaning that he couldn't count on a fixed position to request the Index. In order to accomodte that scenario, try this method, which will look for the index of ExchangeActiveSyncDevices and then pick the index position before that.
$_.Identity.Split('/')[($_.Identity.Split('/').Indexof('ExchangeActiveSyncDevices')-1)]
Get-Mailbox -resultsize unlimited|foreach {Get-MobileDeviceStatistics -Mailbox:$_.identity} |Select-Object #{l="user";e={$_.Identity.parent.parent.name}}, lastsuccesssync
on e2k13
Related
I'm trying to loop all disabled users through an array of groups to check if the users have membership in any of the listed groups. My thought is that for every user in the list loop them through and check if they are present in one of the listed groups. That would require nesting foreach loops, right? The output I get is like this:
...
user1
user2
user3
is not a member of group1
Here is the source code:
$dUsers = Get-ADUser -Filter {enabled -eq $false} |
FT samAccountName |
Out-String
$groups = 'Group1', 'Group2'
foreach ($dUser in $dUsers) {
foreach ($group in $groups) {
$members = Get-ADGroupMember -Identity $group -Recursive |
Select -ExpandProperty SamAccountName
if ($members -contains $dUsers) {
Write-Host "[+] $dUser is a member of $group"
} else {
Write-Host "[-] $dUser is not a member of $group"
}
}
}
I'm pulling my hair because I feel like there is a simple solution, but I'm lost.
Update:
I wanted to put all disabled users in variable $dUsers.
It actually works if I manually put users in the variable like this:
$dUsers = 'user1','user2','user3'
Which gives me the following output:
user1 is not a member of group1
user1 is not a member of group2
user2 is not a member of group1
user2 is not a member of group2
...
This makes me question how it gets "foreached" when the variable is:
$dUsers = Get-ADUser -Filter {enabled -eq $false} |
FT samAccountName |
Out-String
Anyone got a clarification on that?
Update:
This is the final code. It takes a long time to run, even with only two groups.
$dUsers = Get-ADUser -Filter {enabled -eq $false} | Select-Object -Expand SamAccountName
$groups = 'Group1', 'Group2'
Write-host '[+] Checking if any disabled user is member of any SSL groups'
Write-host '[+] This might take a while. Get a coffee!'
write-host '[+] Running...'`n
foreach ($dUser in $dUsers) {
foreach ($group in $groups) {
$members = Get-ADGroupMember -Identity $group -Recursive | Select -ExpandProperty SamAccountName
if($members -contains $dUser) {
Write-Host "$dUser is a member of $group"
} Else {
# Remove or comment out the line below to get a clutterfree list.
# Write-Host "$dUser is not a member of $group"
}
}
}
You have two issues in your code:
You're creating a single string from the Get-ADUser output. Piping the output of that cmdlet through Format-Table (alias ft) and then Out-String creates one string with a tabular display of all matching account names including the table header.
If you output $dUsers in a way that makes beginning and end of a string visible you'd see something like this (the leading and trailing == marking the beginning and end):
PS> $dUsers | ForEach-Object { "==$_==" }
==samAccountName
--------------
user1
user2
user3==
Since there is no account with a username matching this string no match can be found in any group and you're getting the output you observed.
This misuse of Format-* cmdlets is a common beginner's mistake. People get a nicely formatted string output and then try to work with that. ONLY use Format-* cmdlets when you're presenting data directly to a user, NEVER when further processing of the data is required or intended.
What you actually want is not a string with a tabular display of usernames, but an array of username strings. You get that by expanding the SamAccountName property of the user objects you get from Get-ADUser.
$dUsers = Get-ADUser ... | Select-Object -Expand SamAccountName
The second issue is probably just a typo. Your condition $members -contains $dUsers won't work, since both $members and $dUsers are arrays (after fixing the first issue, that is). The -contains operator expects an array as the first operand and a single value as the second operand.
Change
$members -contains $dUsers
to
$members -contains $dUser
Depending on what PowerShell version you are on, there is a cmdlet for this use case and others.
As for
I'm Trying to loop all disabled users
Just do...
Search-ADAccount -AccountDisabled |
Select-Object -Property Name, Enabled,
#{Name = 'GroupName';Expression = {$_.DistinguishedName.Split(',')[1] -replace 'CN='}}
# Results
Name Enabled GroupName
---- ------- ---------
...
testuser2 NewTest False Users
Guest False Users
Or different cmdlet…
# Get disabled users and their group membership, display user and group name
ForEach ($TargetUser in (Get-ADUser -Filter {Enabled -eq $false}))
{
"`n" + "-"*12 + " Showing group membership for " + $TargetUser.SamAccountName
Get-ADPrincipalGroupMembership -Identity $TargetUser.SamAccountName | Select Name
}
# Results
...
------------ Showing group membership for testuser1
Domain Users
Users
------------ Showing group membership for testuser2
Domain Users
As for ...
an array of Groups
Just select or filter the DN for the group name you want using the normal comparison operators.
As for...
Unfortunately I'm not well versed in powershell.
… be sure to spend the necessary time to get ramped up on it, to limit the amount of misconceptions, confusions, errors, etc. that you are going to encounter. There are plenty of no cost / free video and text-based training / presentations all over the web.
Example:
Videos
Use tools that will write the code for you that you can later tweak as needed.
Step-By-Step: Utilizing PowerShell History Viewer in Windows Server 2012 R2
Learning PowerShell with Active Directory Administrative Center (PowerShell History Viewer)
As well as plenty of sample scripts and modules via the MS PowerShell Script / Module Gallery.
There are two commands for the AD Groups.
First I see that you want the membership of the disabled users that is easy.
#Get the dissabled users from your AD with all their attributes (properties and select)
$dUsers = Get-ADUser -Filter {Enabled -eq $false} -Properties * | Select *
#Run a loop for each user to get the group membership
Foreach ($User in $dUsers) {
$User = $User.SamAccountName
Get-ADUser $User -Properties * | Select Name, SamAccountName, MemberOf | Format-Table -Wrap # > "D:\test\$user.txt" -HideTableHeaders
}
This one can work but I don't like the output that we get.
I prefer to run the groupmembership command and check the users.
$GroupMembers = Get-ADGroupMember "groupname"| Select Name, SamAccountName
ForEach ($User in $GroupMembers)
{
$UserProperties = Get-ADUser $User.SamAccountName -Properties * | select *
If ($UserProperties.Enabled -eq $False) {
Write-Host $UserProperties.SamAccountName
}
}
Edit:
Let me know if those fits you.
Kind regards.
The first thing you should try to check is whenever you are only interested in direct memberships or indirect ones as well. Depending on the answer the options you got availabel change a bit. You probably will encounter Distinguished Names while working on this so check out what they are if you don't know (mostly a path for an object).
If it's only direct memberships using memberOf with Get-ADUser should be sufficient. The memberOf attribute contains every direct group membership of the user with the full Distinguished Name of the group.
Get-ADUser test -Properties MemberOf | Select-Object -ExpandProperty memberOf
You can match the groups you're looking for in various ways. You could get the whole Distinguished Name of those groups or you could do a partial match. It's up to you to decide how to proceed.
If you need the indirect memberships as well you might want to split up your code to make it easier for yourself. For instance you could first find the users and save them. Afterwards find all group members of those groups (You already got that with Get-ADGroupMember) and finally compare the two.
Currently for every user you build the whole list of group members again. This approach would save a few resources as you wouldn't be doing the same queries over and over again.
Finally you could also use the MemberOf approach but get the list of every direct and indirect membership of a user using an LDAP query.
$dn = (Get-ADUser example).DistinguishedName
$userGroups = Get-ADGroup -LDAPFilter ("(member:1.2.840.113556.1.4.1941:={0})" -f $dn)
This approach uses a LDAP search query. It can be quite complex, you could also only check for one one of the groups by modifying it a bit.
In the end even your current approach should work. The problem is that you're comparing the AD object against the list of SAM Accountnames. You would need to check for the SAM Accountnames as well.
if($members -contains $dUsers.SamAccountName)
if($members -contains $dUsers | Select-Object -ExpandProperty SamAccountName)
One of these should work if you change your $dUsers as well. As it currently is you end up with a giant string. You probably can check that by checking $dUsers.length. Just drop the Format-Table and Out-String.
I'm writing a script to be used by help desk staff to quickly (and accurately) create AD user accounts by inputting basic employee information. We use portions of a persons name in order to create the samAccountName. Which I've achieve by the following.
$GivenName = "Joe"
$Surname = "Smith"
$SamAccountName = $Surname.substring(0, [System.Math]::Min(5, $Surname.Length)) + $GivenName.Substring(0,1)
This works great as long as there isn't already a "Joe Smith" (or John or Jennifer Smithers, etc). The solution would be to add a number to the end. When manually creating accounts the help desk would search AD look at what number suffix to use if necessary. I'm trying to figure out how PowerShell can do that for us. I've gone through several ideas with help from what I've found online but so far I've been unsuccessful.
My first thought was to do something like this.
$SamSuffix = 2
If ((Get-ADUser -LDAPFilter "(SamAccountName=$samAccountName)")-eq $Null)
{
"$SamAccountName does not exist in AD" #out result for testing.
}
Else{
do
{
Get-ADUser -LDAPFilter "(SamAccountName=$SamAccountName + $SamSuffix++)"
}
until (Get-ADUser -LDAPFilter "(SamAccountName=$SamAccountName + $SamSuffix)")-eq $Null)
}
This obviously doesn't work. Even if it did I don't know how I'd get to the 'next step' to create the account.
I also tried pulling the existing names into a list
$SamExist = (Get-ADUser -LDAPFilter "(SamAccountName=$SamAccountName*)" | Select SamAccountName)
do {$SamAccountName + $SamSuffix++}
until ($SamExist -notcontains $SamAccountName -or $SamAccountName + $SamSuffix)
This also doesn't work but if it did I can see that it would automatically add the suffix even if it wasn't needed.
You approach where you get all the existing matches first would be where I would start. Lets assume $SamAccountName is smithj
$existingAccounts = Get-ADUser -Filter "samaccountname -like '$SamAccountName*'" -ErrorAction SilentlyContinue | Select-Object -ExpandProperty samaccountname
So $existingaccounts that have a samaccountname's starting with smithj. If there are not any then $existingAccounts would be null and we can test for that.
if($existingAccounts){
# Figure out what the suffix will be
$highestValue = $existingAccounts -replace "^$SamAccountName" |
ForEach-Object{[int]$_} |
Measure-Object -Maximum |
Select-Object -ExpandProperty Maximum
} else {
# Create the user as normal.
}
Pretending there are some accounts that exist we trim the leading characters from the samaccountname, convert the remaining to an integer and select the highest from that. So $highestValue is the last number used in a conflicting account.
Add one to that and you have a guaranteed username you can create assuming nothing changes in those moments i.e. two users making to smithj accounts.
If you are looking to fill gaps where a user might have left and you want to use the next available after 1 then you could do this.
$existingAccounts = "smithj1", "smithj5", "smithj10", "smithj2", "smithj3"
# Figure out what the next unused suffix will be
$existingSuffixes = $existingAccounts -replace "^$SamAccountName" | ForEach-Object{[int]$_}
# Once again determine the maximum one in use
$highestValue = $existingSuffixes | Measure-Object -Maximum | Select-Object -ExpandProperty Maximum
# Find the first gap between 1 and the max suffix
$nextAvailableSuffix = 1..($highestValue + 1) | Where-Object{$existingSuffixes -notcontains $_} | Sort-Object desc | Select -First 1
$nextAvailableSuffix would contain 4 using the above example. We add 1 to highest value in case the only other one is 2 so that way there will only be an answer to $nextAvailableSuffix
I'm trying to go through a list of users I have and would like to get a few properties (DisplayName, Office) to show in a table then convert the table to a .csv.
I've been working with:
$Users = gc "C:\scripts\Users.txt
foreach ($User in $Users) {
Get-ADUser -Identity $User -Properties DisplayName,Office
}
And that's fine, I can combine it with "select DisplayName,Office" but if I do an "Out-File -append" it just looks terrible. I think I should do this with an array or hash table but I've been reading up on them and don't really understand how I would automate one that can be exported. Thanks for any help you can provide.
Query all users and filter by the list from your text file:
$Users = Get-Content 'C:\scripts\Users.txt'
Get-ADUser -Filter '*' -Properties DisplayName,Office |
Where-Object { $Users -contains $_.SamAccountName } |
Select-Object DisplayName, Office |
Export-Csv 'C:\path\to\your.csv' -NoType
Get-ADUser -Filter '*' returns all AD user accounts. This stream of user objects is then piped into a Where-Object filter, which checks for each object if its SamAccountName property is contained in the user list from your input file ($Users). Only objects with a matching account name are passed forward to the next step of the pipeline. The output can be limited by selecting the relevant properties before exporting the data.
You can further optimize the code by replacing the -contains operator with hashtable lookups:
$Users = #{}
Get-Content 'C:\scripts\Users.txt' | ForEach-Object { $Users[$_] = $true }
Get-ADUser -Filter '*' -Properties DisplayName,Office |
Where-Object { $Users.ContainsKey($_.SamAccountName) } |
Select-Object DisplayName, Office |
Export-Csv 'C:\path\to\your.csv' -NoType
This can be simplified by completely skipping the where object and the $users declaration. All you need is:
Code
get-content c:\scripts\users.txt | get-aduser -properties * | select displayname, office | export-csv c:\path\to\your.csv
#AnsgarWiechers - it's not my experience that querying everything and then pruning the result is more efficient when you're doing a targeted search of known accounts. Although, yes, it is also more efficient to select just the properties you need to return.
The below examples are based on a domain in the range of 20,000 account objects.
measure-command {Get-ADUser -Filter '*' -Properties DisplayName,st }
...
Seconds : 16
Milliseconds : 208
measure-command {$userlist | get-aduser -Properties DisplayName,st}
...
Seconds : 3
Milliseconds : 496
In the second example, $userlist contains 368 account names (just strings, not pre-fetched account objects).
Note that if I include the where clause per your suggestion to prune to the actually desired results, it's even more expensive.
measure-command {Get-ADUser -Filter '*' -Properties DisplayName,st |where {$userlist -Contains $_.samaccountname } }
...
Seconds : 17
Milliseconds : 876
Indexed attributes seem to have similar performance (I tried just returning displayName).
Even if I return all user account properties in my set, it's more efficient. (Adding a select statement to the below brings it down by a half-second).
measure-command {$userlist | get-aduser -Properties *}
...
Seconds : 12
Milliseconds : 75
I can't find a good document that was written in ye olde days about AD queries to link to, but you're hitting every account in your search scope to return the properties. This discusses the basics of doing effective AD queries - scoping and filtering: https://msdn.microsoft.com/en-us/library/ms808539.aspx#efficientadapps_topic01
When your search scope is "*", you're still building a (big) list of the objects and iterating through each one. An LDAP search filter is always more efficient to build the list first (or a narrow search base, which is again building a smaller list to query).
This is directed towards Exchange/Office 365 powershell but can apply for other types as well.
If i have a list of users for example, and I use UserPrincipalName for their identity - now in all my scripts I can pull data using UserPrincipalName for the identity, but in most UserPrincipalName isn't actually an exportable value. So, if you take my two examples below I can pull data in both using UserPrincipalName for the identity, but in the 2nd one the ouput would be blank.
How can I insert for example the identity value UserPrincipalName into the 2nd. This would apply for more than the two scripts mentioned below.
$CSV | foreach {
Get-Mailbox -resultsize unlimited -Identity $_.UserPrincipalName
} | select userprincipalname, server |out-gridview
Example output:
UPN#something.com, server101
$csv | ForEach {
Get-ActiveSyncDeviceStatistics -Identity:$_.UserPrincipalName
} | select DeviceFriendlyName,UserPrincipalName |out-gridview
Example output:
2nd: DeviceIOS4,-
I don't have an Exchange at hand, but most likely the objects returned by Get-Mailbox do have a UserPrincipalName property, while the objects returned by Get-ActiveSyncDeviceStatistics don't. To preserve the UPN in the output of the second script, you can do something like this:
$csv | ForEach {
$upn = $_.UserPrincipalName
Get-ActiveSyncDeviceStatistics -Identity:$upn
} | select DeviceFriendlyName,#{n='UserPrincipalName';e={$upn}} |out-gridview
I'm trying to create simple reports on Exchange 2010 mailbox size.
While this works:
Get-MailboxStatistics -server <serverfqdn> |ft displayname, TotalItemSize
this doesn't (second column stays empty):
Get-MailboxStatistics -server <serverfqdn> |ft displayname, {$_.TotalItemSize.Value.ToBytes()}
The problem is that I need the size as an integer, so the first line of code doesn't serve my purpose.
According to several websites the second line of code should work but unfortunately doesn't on my machine.
I know I could parse the value after using the first line but that would be unnecessarily inefficient, wouldn't it? :-)
Hope anyone can help.
Regards,
Kevin
If performing from a imported PS session the methods .ToMB() is lost as the type becomes a custom object.
The $variable.TotalItemSize.Value becomes a two element array [0] in KB,MB or GB and [1] always in bytes.
So to use this we can play with strings to achieve what we want.. in long hand for clarity
$mailboxes = Get-Mailbox -Filter{(RecipientType -eq "UserMailbox") -and (CustomAttribute12 -eq "whatever")}
foreach ($mailbox in $mailboxes)
{
$size1 = Get-MailboxStatistics $mailbox.id
[string]$bob = $size1.TotalItemSize.Value
[int]$bill = $bob.TrimEnd(" bytes)").Split("(")[1] # The bytes part of the array.
$bill = $bill/1mb # Convert into MB's
if ($bill -le 1500) {do something} Else {"Too Big " + $bill} # note -le 1500 NOT 1500MB
}
I hope this helps
this worked for me
$a = get-mailbox -id user | Get-MailboxStatistics
$a.TotalItemSize.Value.ToMB()
$a.TotalItemSize.Value.ToKB()
I have the same issue. I'm not sure if you resolved this.
I have this, which is quite ugly - but works:
$a = get-mailbox USER | get-mailboxstatistics
$intTotalItemSize = [int]$a.TotalItemSize.SubString($a.TotalItemSize.indexof("(")+1, $a.TotalItemSize.indexof(" b")-$a.TotalItemSize.indexof("("))
Try this for your size expression:
#{expression={$_.TotalItemSize.Value.ToMB()};label="Mailbox Size(MB)"}
I believe there is also a ToKB() method.
MVP Shay Levy has delved into this on his blog (http://blogs.microsoft.co.il/blogs/scriptfanatic/archive/2011/08/22/get-full-control-over-your-exchange-remote-powershell-session.aspx).
Basically, you have to modify a setting in the PowerShell virtual directory on the server that you are remoting to.
This is great news for those who are remoting to Exchange servers that they have this kind of control over, but is not helpful for those of us who use hosted Exchange solutions and cannot change these settings. I suppose we will just have to abandon some of the uber-coolness of PowerShell and go back to parsing the string to get the bytes and convert from there.
--EDIT--
This is how I tackled outputting a file of all of my users' mailbox sizes. It could be compressed a bit further, but is a little more readable this way.
$allMailboxes = Get-Mailbox -ResultSize Unlimited
ForEach ( $mailbox in $allMailboxes ) {
$itemSizeString = ( Get-MailboxStatistics $mailbox.Identity ).TotalItemSize.Value.ToString()
$posOpenParen = $itemSizeString.IndexOf("(") + 1
$numCharsInSize = $itemSizeString.IndexOf(" bytes") - $posOpenParen
$mailboxSizeInBytes = $itemSizeString.SubString($posOpenParen,$numCharsInSize).Replace(",","")
Write-Output "$($mailbox.alias),$($mailboxSizeInBytes)"
}
Please, see this article: http://blogs.technet.com/b/gary/archive/2010/02/20/the-get-mailboxstatistics-cmdlet-the-totalitemsize-property-and-that-pesky-little-b.aspx
Get-Mailbox | Get-MailboxStatistics | Add-Member -MemberType ScriptProperty -Name TotalItemSizeinMB -Value {$this.totalitemsize.value.ToMB()} -PassThru | Format-Table DisplayName,TotalItem*
I needed to have this work outside of a remoting session, so I simplified the answer from Greybear to this:
$a = get-mailbox USER | get-mailboxstatistics
$intTotalItemSize = [int64]($a.TotalItemSize -split '[\( ]')[3]
Or in the format of the original question::
Get-MailboxStatistics -Server <serverfqdn> | Select-Object -Property DisplayName,#{label="TotalItemSize";expression={[int64]($_.TotalItemSize -split '[\( ]')[3]}} | ft
Realized that [int] would fail for mailboxes over 4GB, so changed to [int64]. Alternately, display the mailboxes in MB:
Get-MailboxStatistics -Server <serverfqdn> | Select-Object -Property DisplayName,#{label="TotalItemSize";expression={[int64](([int64]($_.TotalItemSize -split '[\( ]')[3])/1048576)}} | ft
The name needs to go before the expression. This will work.
Get-MailboxStatistics -Identity [name] | select #{label=”User”;expression={$_.DisplayName}},lastlogontime,#{label=”Total Size (MB)”;expression={$_.TotalItemSize.Value.ToMB()}}
This works for me
#{Name="TotalSize (MB)"; Expression={((($_.TotalItemSize) -split " ")[0])}}
You might try:
get-mailbox -resultsize unlimited | Get-MailboxStatistics | ft displayname,#{label="Total Size (MB)";expression={$_.TotalItemSize.Value.ToMB()}}