I am trying to figure out how to get a value from one command as a parameter to another command, and use the output of both in a single table. Specifically I am using two cmdlets, Get-Mailbox and Get-MailboxStatistics (this is against an Exchange 2010 server).
From Get-Mailbox I need the DisplayName, UseDatabaseQuotaDefaults and Database fields. Added to this, I need to get from Get-MailboxStatistics the TotalItemSize and StorageLimitStatus fields.
I can run each of these commands individually, but cannot figure out how to use the DisplayName value from Get-Mailbox fed into the Identity value for Get-MailboxStatistics command and then output the whole lot into a single table.
I was trying something along these lines:
get-mailbox | ForEach-Object {write-host $_.DisplayName, $_.UseDatabaseQuotaDefaults, $_.Database, Get-MailboxStatistics $_.SamAccountName}
Instead of actually processing the Get-MailboxStatistics as a command it just display it as text. How can I get PS to treat this as a command and not text for the write-host cmdlet?
You need to use parentheses, something along these lines:
get-mailbox | ForEach-Object { Write-Host `
$_.DisplayName, `
$_.UseDatabaseQuotaDefaults, `
$_.Database, `
(Get-MailboxStatistics $.SamAccountName) }
# ^------- note the parentheses ---------^
Using bits of info from the previous answers, combined with some Google searching and more than my fair share of cursing, came up with the following script that actually works:
$mbx = Get-Mailbox
ForEach ($cur in $mbx)
{
$stat = (Get-MailboxStatistics $cur.DisplayName)
New-Object PSObject -Property #{
DisplayName = $cur.DisplayName
UseDatabaseQuotaDefaults = $cur.UseDatabaseQuotaDefaults
SamAccountName = $cur.SamAccountName
StorageLimitStatus = $stat.StorageLimitStatus
TotalItemSize = $stat.TotalItemSize
Database = $stat.Database
}
}
thanks everyone!
Get a list of all mailboxes, for each one assign its statistics to a variable, then create a custom object with properties from both objects:
Get-Mailbox | ForEach-Object {
$stats = $_ | Get-MailboxStatistics
New-Object PSObject -Property #{
DisplayName = $_.DisplayName
UseDatabaseQuotaDefaults = $_.UseDatabaseQuotaDefaults
Database = $_.Database
SamAccountName = $_.SamAccountName
TotalItemSize = $stats.TotalItemSize
StorageLimitStatus = $stats.StorageLimitStatus
}
}
Related
I am trying to gather some information on disabled user accounts that have mailboxes. I am specifically looking for just user mailboxes not shared mailboxes.
Here is what I have so far.
$Mailboxes = Get-Mailbox | where {$_.RecipientTypeDetails -eq 'UserMailbox'}
$date = get-date -f "MMddyyyy_HHmm"
$Disabled = #()
Foreach ($Mailbox in $Mailboxes) {
if((Get-ADUser -Identity $Mailbox.SamAccountName).Enabled -eq $False){
$Disabled += Get-MailboxStatistics $Mailbox.SamAccountName | Select -Property DisplayName,TotalItemSize
}
}
$Disabled | Sort DisplayName | Export-Csv -Path "%path%\DisabledADUsersWithMailbox_$date`.csv" -NoTypeInformation
Additionally what I would like to collect is the users Title, Manager, LastlogonDate all of which can be found using Get-Aduser. I am unsure how I go about collecting the information from both cmdlets and then exporting it all to csv. I have read that I may need to create a custom object. I am struggling with setting that up in this script.
Any help would be much appreciated.
Thanks
the following lines should give you what you want, can't verify it as I have no exchange running here.
$date = get-date -f "MMddyyyy_HHmm"
$Disabled = #(
Foreach ($Mailbox in $Mailboxes) {
$adUser = get-aduser -Identity $Mailbox.SamAccountName -Properties enabled,manager,title,lastlogontimestamp
If ($adUser.Enabled -eq $False){
$mailStats = Get-MailboxStatistics $Mailbox.SamAccountName
$attrsht = [ordered]#{
displayname=$mailstats.displayname
totalitemsize=$mailStats.totalitemsize
samaccountname=$aduser.samaccountname
enabled=$aduser.enabled
manager=$aduser.manager
title=$aduser.title
lastlogontimestamp=[datetime]::FromFileTime($aduser.lastlogontimestamp)
}
new-object -TypeName psobject -Property $attrsht
}
}
)
$Disabled | Sort-Object DisplayName | Export-Csv -Path "%path%\DisabledADUsersWithMailbox_$date`.csv" -NoTypeInformation
Avoid adding elements to an array by using +=. It is slow, alternatively take a look at generic array lists.
I am trying to Import a list of endpoints I have which is saved to a CSV, Loop through the list and Display the DeviceName, Owner, Depatment, Jobtitle. Please help.
$Endpoints = Import-Csv -Path "C:\Users\Ryan\Endpoints.csv"
$Result = #()
$Users = Get-AzureADDevice -filter $Endpoints.Endpoint | select DisplayName, objectID
$Users | ForEach -Object {
$user = $_
Get-AzureADDeviceRegisteredOwner -ObjectId $user.ObjectId -All $true |
ForEach -Object {
$Result += New-Object PSObject -property #{
DeviceOwner = $_.DisplayName
DeviceName = $user.Displayname
Department = $_.Department
Jobtitle = $_.Jobtitle
}
}
}
$Result | Select DeviceOwner, DeviceName, Department, Jobtitle
I had this working without Import using a static Endpoint Name e.g. $Users = Get-AzureADDevice -SearchString "LaptopName"
Based on assumptions this might be what you're looking for, note the outer loop to iterate over each row of your CSV:
Import-Csv -Path "C:\Users\Ryan\Endpoints Society\Desktop\Endpoint.csv" | ForEach-Object {
$devices = Get-AzureADDevice -SearchString $_.Endpoint
if(-not $devices) { return "$($_.EndPoint) not found, skipping" }
foreach($device in $devices) {
foreach($owner in Get-AzureADDeviceRegisteredOwner -ObjectId $device.ObjectId -All $true) {
[pscustomobject]#{
DeviceOwner = $owner.DisplayName
DeviceName = $device.Displayname
Department = $owner.Department
Jobtitle = $owner.Jobtitle
}
}
}
}
Not your answer, but how to find it
This script suffers greatly from 'one-liner-itis'. It is very hard to follow and going to be impossible to debug because of its over-the-top reliance on pipes and foreach-object commands§
Step one to make this even testable is to unwrap these commands into stand-alone commands, then you can debug line by line and see what the values are at that line.
$Endpoints = Import-Csv -Path "C:\Users\Ryan\Endpoints.csv"
$Result=#()
$devices= Get-AzureADDevice -filter $Endpoints.Endpoint |
select DisplayName, objectID
#for debugging, we will only look at the first two devices
$devices = $devices[0..1]
foreach($device in $devices){
$thisDeviceOwners = $_Get-AzureADDeviceRegisteredOwner -ObjectId $user.ObjectId -All $true
foreach($owner in $thisDeviceOwners){
$Result += New-Object PSObject -property #{
DeviceOwner = $_.DisplayName
DeviceName = $user.Displayname
Department = $_.Department
Jobtitle = $_.Jobtitle
}
}
}
$Result | Select DeviceOwner, DeviceName, Department, Jobtitle
Next, just run this one time in your IDE of choice, like PowerShell ISE or Visual Studio Code, then look to see what values you have for these variables. One of them will be empty or odd, and therein lies your problem:
$device
$devices
$thisDeviceOwners
$owner
Note that I changed $devices to only be the first two items in $devices, so you can see how the code is working. Find and fix your problem with one or two items and then the whole script will likely work.
§ - there is nothing wrong with your approach, and in fact PowerShell was built from the ground up to be like the Unix Bash Shell, loads of small commands that do one thing, and can be used to pass results on to the next command. It allows for ad-hoc powerful control.
But in an enterprise script, we should not use one-liners and should instead use commonly accepted coding practices. Each line of code should do only one thing, and shouldn't wrap around.
I am trying to export list of users in mail enabled security groups to csv but want to have each member in a separate column rather than joining the existing column.
$Csvfile = "C:\SPOgroupmembers.csv"
$Groups = Get-DistributionGroup -Filter "Alias -like '*.spo'" -ResultSize Unlimited
$Groups | ForEach-Object {
$GroupDN = $_.DistinguishedName
$DisplayName = $_.DisplayName
$PrimarySmtpAddress = $_.PrimarySmtpAddress
$Members = Get-DistributionGroupMember $GroupDN -ResultSize Unlimited
[PSCustomObject]#{
DisplayName = $DisplayName
PrimarySmtpAddress = $PrimarySmtpAddress
Members = ($Members.Name -join ',')
}
} | Sort-Object DisplayName | Export-CSV -Path $Csvfile -NoTypeInformation -Encoding UTF8 #-Delimiter ";"
This is how it currently outputs:
DisplayName
PrimarySmtpAddress
Member
Test.SPO
Test.SPO#test.com
User1,User2,User3
This is what I am trying to achieve:
DisplayName
PrimarySmtpAddress
Test.SPO
Test.SPO#test.com
User1
User2
I may be missing something simple but any help would be appreciated
If you want to export the data where each member has it's own row, which in my opinion, would be the proper way to do it, you can have an inner loop to create a new pscustomobject per member of the Group:
Get-DistributionGroup -Filter "Alias -like '*.spo'" -ResultSize Unlimited | ForEach-Object {
foreach($member in Get-DistributionGroupMember $_.DistinguishedName -ResultSize Unlimited) {
[PSCustomObject]#{
DisplayName = $_.DisplayName
PrimarySmtpAddress = $_.PrimarySmtpAddress
Member = $member
}
}
} | Sort-Object DisplayName | Export-CSV -Path ....
The simplest way to construct a [pscustomobject] dynamically is to construct an ordered hashtable first - which is easy to extend iteratively - and cast it to [pscustomobject] when done.
However, in the context of creating CSV output, you need to commit to a fixed number of properties (columns) ahead of time - if feasible[1]; e.g.:
$maxMembers = 10 # <- adjust this number to the max. count of members you expect
$Groups | ForEach-Object { ...
# ...
# Initialize an ordered hashtable with the static entries...
$oht = [ordered] #{
DisplayName = $DisplayName
PrimarySmtpAddress = $PrimarySmtpAddress
}
# ... then iteratively add the Member1, Member2, ... entries
foreach ($i in 1..$maxMembers) {
$oht["Member$i"] = $Members[$i-1]
}
# Convert to a [pscustomobject] and output
[pscustomobject] $oht
} | Sort-Object DisplayName | Export-CSV -Path $Csvfile -NoTypeInformation -Encoding UTF8
If no max. member count can / should be assumed, consider denormalizing the data by using a single member column combined with creating a separate row for each member, as shown in Santiago's helpful answer, which is unquestionably the better approach for subsequent programmatic processing of the data vs. the multi-column approach you're seeking, which may be simpler to grasp for the human observer.
[1] That is, you need to know how many members a group can have at most. You could even try to determine that count programmatically, ahead of time, but either way the resulting number may be too large to be practical.
I have the below working script that checks if a large list of users in a CSV file are a member of an AD group and writes the results to results.csv.
Not sure how to convert the script so I can change $group = "InfraLite" to $group = DC .\List_Of_AD_Groups.CSV.
So the script doesn't just return matches for one AD group but so it returns matches for the 80 AD groups contained in the List_of_AD_groups.csv also. Writing a YES/NO for each AD group in a new column in the CSV (or if that's not possible creating a seperate .csv file for each group with results would do also.
I could do this manually by changing the value of $group and export file name, and re-running the script 80 times but must be a quick was with PS to do this?
e.g. results.csv:
NAME AD_GROUP1 AD_GROUP2 AD_GROUP80 etc etc.
user1 yes no yes
user2 no no yes
user3 no yes no
echo "UserName`InfraLite" >> results.csv
$users = GC .\user_list.csv
$group = "InfraLite"
$members = Get-ADGroupMember -Identity $group -Recursive |
Select -ExpandProperty SAMAccountName
foreach ($user in $users) {
if ($members -contains $user) {
echo "$user $group`tYes" >> results.csv
} else {
echo "$user`tNo" >> results.csv
}
}
I played with this for a while, and I think I found a way to get you exactly what you were after.
I think Ansgar was on the right path, but I couldn't quite get it to do what you were after. He mentioned that he didn't access to an AD environment at the time of writing.
Here is what I came up with:
$UserArray = Get-Content 'C:\Temp\Users.txt'
$GroupArray = Get-Content 'C:\Temp\Groups.txt'
$OutputFile = 'C:\Temp\Something.csv'
# Setting up a hashtable for later use
$UserHash = New-Object -TypeName System.Collections.Hashtable
# Outer loop to add users and membership to UserHash
$UserArray | ForEach-Object{
$UserInfo = Get-ADUser $_ -Properties MemberOf
# Strips the LPAP syntax to just the SAMAccountName of the group
$Memberships = $UserInfo.MemberOf | ForEach-Object{
($_.Split(',')[0]).replace('CN=','')
}
#Adding the User=Membership pair to the Hash
$UserHash.Add($_,$Memberships)
}
# Outer loop to create an object per user
$Results = $UserArray | ForEach-Object{
# First create a simple object
$User = New-Object -TypeName PSCustomObject -Property #{
Name = $_
}
# Dynamically add members to the object, based on the $GroupArray
$GroupArray | ForEach-Object {
#Checking $UserHash to see if group shows up in user's membership list
$UserIsMember = $UserHash.($User.Name) -contains $_
#Adding property to object, and value
$User | Add-Member -MemberType NoteProperty -Name $_ -Value $UserIsMember
}
#Returning the object to the variable
Return $User
}
#Convert the objects to a CSV, then output them
$Results | ConvertTo-CSV -NoTypeInformation | Out-File $OutputFile
Hopefully that all makes sense. I commented as much of it as I could. It would be very simple to convert to using ADSI if you didn't have RSAT installed on whatever machine you're running this on. If you need that let me know, and I'll make some quick modifications.
I've also tossed a slightly modified version of this in a Gist for later reference.
The trivial solution to your problem would be to wrap your existing code in another loop and create an output file for each group:
$groups = Get-Content 'C:\groups.txt'
foreach ($group in $groups) {
$members = Get-ADGroupMember ...
...
}
A more elegant approach would be to create a group mapping template, clone it for each user, and fill the copy with the user's group memberships. Something like this should work:
$template = #{}
Get-Content 'C:\groups.txt' | ForEach-Object {
$template[$_] = $false
}
$groups = #{}
Get-ADGroup -Filter * | ForEach-Object {
$groups[$_.DistinguishedName] = $_.Name
}
Get-ADUser -Filter * -Properties MemberOf | ForEach-Object {
$groupmap = $template.Clone()
$_.MemberOf |
ForEach-Object { $groups[$_] } |
Where-Object { $groupmap.ContainsKey($_) } |
ForEach-Object { $groupmap[$_] = $true }
New-Object -Type PSObject -Property $groupmap
} | Export-Csv 'C:\user_group_mapping.csv' -NoType
I want to be able to output data from PowerShell without any column headings. I know I can hide the column heading using Format-Table -HideTableHeaders, but that leaves a blank line at the top.
Here is my example:
get-qadgroupmember 'Domain Admins' | Select Name | ft -hide | out-file Admins.txt
How do I eliminate the column heading and the blank line?
I could add another line and do this:
Get-Content Admins.txt | Where {$_ -ne ""} | out-file Admins1.txt
But can I do this on one line?
In your case, when you just select a single property, the easiest way is probably to bypass any formatting altogether:
get-qadgroupmember 'Domain Admins' | foreach { $_.Name }
This will get you a simple string[] without column headings or empty lines. The Format-* cmdlets are mainly for human consumption and thus their output is not designed to be easily machine-readable or -parseable.
For multiple properties I'd probably go with the -f format operator. Something along the lines of
alias | %{ "{0,-10}{1,-10}{2,-60}" -f $_.COmmandType,$_.Name,$_.Definition }
which isn't pretty but gives you easy and complete control over the output formatting. And no empty lines :-)
A better answer is to leave your script as it was. When doing the Select name, follow it by -ExpandProperty Name like so:
Get-ADGroupMember 'Domain Admins' | Select Name -ExpandProperty Name | out-file Admins.txt
If you use "format-table" you can use -hidetableheaders
add the parameter -expandproperty after the select-object, it will return only data without header.
The -expandproperty does not work with more than 1 object. You can use this one :
Select-Object Name | ForEach-Object {$_.Name}
If there is more than one value then :
Select-Object Name, Country | ForEach-Object {$_.Name + " " + $Country}
Joey mentioned that Format-* is for human consumption. If you're writing to a file for machine consumption, maybe you want to use Export-*? Some good ones are
Export-Csv - Comma separated value. Great for when you know what the columns are going to be
Export-Clixml - You can export whole objects and collections. This is great for serialization.
If you want to read back in, you can use Import-Csv and Import-Clixml. I find that I like this better than inventing my own data formats (also it's pretty easy to whip up an Import-Ini if that's your preference).
First we grab the command output, then wrap it and select one of its properties. There is only one and its "Name" which is what we want. So we select the groups property with ".name" then output it.
to text file
(Get-ADGroupMember 'Domain Admins' |Select name).name | out-file Admins1.txt
to csv
(Get-ADGroupMember 'Domain Admins' |Select name).name | export-csv -notypeinformation "Admins1.csv"
$server = ('*')+(Read-Host -prompt "What Server Context?")+'*'
$Report = (Get-adcomputer -SearchBase "OU=serverou,DC=domain,DC=com" -filter {name -like $server} -SearchScope Subtree|select Name |Sort -Unique Name)
$report.Name | Out-File .\output\out.txt -Encoding ascii -Force
$Report
start notepad .\output\out.txt
Put your server SearchBase in above.
If you are not sure what your server OU is try this function below...
#Function Get-OSCComputerOU($Computername)
{
$Filter = "(&(objectCategory=Computer)(Name=$ComputerName))"
$DirectorySearcher = New-Object System.DirectoryServices.DirectorySearcher
$DirectorySearcher.Filter = $Filter
$SearcherPath = $DirectorySearcher.FindOne()
$DistinguishedName = $SearcherPath.GetDirectoryEntry().DistinguishedName
$OUName = ($DistinguishedName.Split(","))[1]
$OUMainName = $OUName.SubString($OUName.IndexOf("=")+1)
# $Obj = New-Object -TypeName PSObject -Property #{"ComputerName" = $ComputerName
# "BelongsToOU" = $OUMainName
# "Full" = $DistinguishedName}
$Obj = New-Object -TypeName PSObject -Property #{"Full" = $DistinguishedName}
$Obj
}
Makes sure to run the Get-OSCComputerOU Servername with a select -expandproperty Full filter.
Then just plug in the response to the Searchbase...
All thanks to http://www.jaapbrasser.com