Get-Mailbox in PowerShell crashes because there's too many mailboxes - arrays

I have a little problem, I want to get all the mailboxes and put them in a variable, which seems easy with
$mailboxes = Get-Mailbox
But there's too much mailboxes in our database (I think around 90k) and PowerShell crashes when I use this command. It works if I have like only 7500 mailboxes, I thought about using mailboxes paging, but how can I do something like:
$allMailBoxes = #()
$mailboxes1 = Get-Mailbox -From 0 -To 10000
$mailboxes2 = Get-Mailbox -From 100001 -To 20000
$allMailBoxes += mailboxes1 += mailboxes2
Or is there any other solution I didn't think about? I need to store this data every week. I only need the numbers, the ones with the voice enterprise option, and the average mailbox size.
Current Code :
## EXCHANGE
$allMailBoxes = Get-Mailbox -resultsize 7500
$CountMailBoxes = $allMailBoxes.count
$CountSharedMailBoxes = (Get-Mailbox -RecipientTypeDetails SharedMailbox -ResultSize:Unlimited).count
#$CountSharedMailLists = voir flo
$CountExchangeDatabases = (Get-MailboxDatabase).Count
$MailBoxesStatistics = $allMailBoxes | Get-MailboxStatistics
$tabMailBoxesSize = #()
foreach ($Mailbox in $MailBoxesStatistics)
{
$MailboxSize = $Mailbox.TotalItemSize
for($i = 0; $i -lt $MailboxSize.Length; $i++) {
if ($MailboxSize[$i] -eq "(")
{
$indexChevron = $i
## formatted quota without the bytes : "1.9 GB <2,040,110,000 bytes>" becomes "1.9 GB"
$FormattedMailboxSize = Invoke-Expression ((($MailboxSize.Substring(0, $indexChevron-1)).replace(' ', '')))
$tabMailBoxesSize += $FormattedMailboxSize
}
}
}
$tabMailBoxesSize = $tabmailboxessize | Measure-Object -Average | select Average
$CountMailBoxesAverageSize = [math]::Round($tabmailboxesSize.Average/1GB, 2).toString() + " GB"
Write-Output "Taille moyenne des BALs : $CountMailBoxesAverageSize"

Related

Get values from 2 arrays

Maybe the header is wrong but i dont know how to explain.
I have 4 csv files with aprox 15000 rows in each looking like this
number,"surname","forename","emailAddress","taxIdentifier"
100238963,"Smith","John","john.smith#gmail.com","xxxxxxxxxxxx"
Im reading in 9999 of the rows and creating a json file we use on a site to check every person, we then get a respond back for most of the users, and that respons is "number"
Then i need to find all them persons in the first array.
I have done it like this today, but it take to much time to check every person like this, is there any better way of doing this?
This is the code for getting the persons from the file and create json file:
$Files = Get-ChildItem -Path "$Folders\\*" -Include *.csv -Force
foreach ($File in $Files){
$fname = $file
$fname = (Split-Path $File.name -leaf).ToString().Replace(".csv", "")
$Savefile = $fname+ "_Cleaned.csv"
$users = Import-Csv $File
$body = "{`"requestId`": `"144x25`",`"items`": ["
$batchSize = 9999
$batchNum = 0
$row = 0
while ($row -lt $users.Count) {
$test = $users[$row..($row + $batchSize - 1)]
foreach ($user in $test) {
$nr = $user.number
$tax = $user.taxIdentifier
$body += "{`"itemId`": `"$nr`",`"subjectId`": `"$tax`"},"
}
And then this is the code to deal with the respons:
$Result = #()
foreach ($1 in $response.allowedItemIds)
{
foreach ($2 in $Users){
If ($2.number -like $1)
{
$Result += [pscustomobject]#{
number = $2.number
Surname = $2.surname
Forename = $2.forename
Email = $2.emailaddress
Taxidendifier = $2.taxIdentifier
}
}
}
}
$Result | Export-Csv -path "$folders\$savefile" -NoTypeInformation -Append
$row += $batchSize
$batchNum++
Hope someone has any ideas
Cheers
I think you can just do this:
# read the original data file
$originalCsv = #"
number,"surname","forename","emailAddress","taxIdentifier"
1000,"Smith","Mel","mel.smith#example.org","xxxxxxxxxxxx"
3000,"Wilde","Kim","kim.wilde#example.org","xxxxxxxxxxxx"
2000,"Jones","Gryff Rhys","gryff.jones#example.org","xxxxxxxxxxxx"
"#
$originalData = $originalCsv | ConvertFrom-Csv
# get a response from the api
$responseJson = #"
{
"requestId": "144x25",
"responseId": "2efb8b47-d693-46ac-96b1-a31288567cf3",
"allowedItemIds": [ 1000, 2000 ]
}
"#
$responseData = $responseJson | ConvertFrom-Json
# filter original data for matches to the response
$matches = $originalData | where-object { $_.number -in $responseData.allowedItemIds }
# number surname forename emailAddress taxIdentifier
# ------ ------- -------- ------------ -------------
# 1000 Smith Mel mel.smith#example.org xxxxxxxxxxxx
# 2000 Jones Gryff Rhys gryff.jones#example.org xxxxxxxxxxxx
# write the data out
$matches | Export-Csv -Path ".\myfile.csv" -NoTypeInformation -Append
I don't know if that will perform better than your example, but it should do as it's not got a nested loop that runs original row count * response row count times.

Using Powershell to grab disk space info from remote servers and add to array

I've been trying to get some scripts together to automate some of the manual tasks that we still do reporting on. This one I'm trying to coax into connecting to each of the remote servers specified (I can AD link and filter it later), pull disk information, do a basic calculation, some formatting, and then stick it in an array to pull later.
I'm currently stuck with errors stating that I'm "attempting to divide by 0", and my array returns no data (I'm assuming because of the above". There has to be something small I'm missing. Well, hopefully small. Here's where I've gotten to:
#Variable listing servers to check. Can convert to a csv, or direct connection to AD
using OU's.
$ServersToScan = #('x, y, z')
#Blank Array for Report
$finalReport = #()
#Threshold Definition %
$Critical = 5
$Warning = 15
#Action for each server
foreach ($i in $ServersToScan)
{
Enter-PSSession -ComputerName $i
#Fixed Disk Info Gather
$diskObj = Get-CimInstance -ClassName Win32_LogicalDisk | Where-Object { $_.DriveType -eq 3 }
#Iterate each disk information - rewrite as foreach ($x in $diskObj) - rewritten 3/31/22
foreach ($diskObj in $diskObj)
{
# Calculate the free space percentage
$percentFree = [int](($_.FreeSpace / $_.Size) * 100)
# Determine the "Status"
if ($percentFree -gt $Warning) {
$Status = 'Normal'
}
elseif ($percentFree -gt $Critical) {
$Status = 'Warning'
}
elseif ($percentFree -le $Critical) {
$Status = 'Critical'
}
# Compose the properties of the object to add to the report
$tempObj = [ordered]#{
'Computer Name' = $i
'Drive Letter' = $_.DeviceID
'Drive Name' = $_.VolumeName
'Total Space (GB)' = [int]($_.Size / 1gb)
'Free Space (GB)' = [int]($_.FreeSpace / 1gb)
'Free Space (%)' = "{0}{1}" -f [int]$percentFree, '%'
'Status' = $Status
}
# Add the object to the final report
$finalReport += New-Object psobject -property $tempObj
}
Exit-PSSession
}
return $finalReport
Any insight would be great - thank you very much!!
There are 2 main problems, the first one, as Jeff Zeitlin pointed out, the automatic variable $_ ($PSItem) has no effect in a foreach loop, it is effectively $null:
[int](($null / $null) * 100)
# => RuntimeException: Attempted to divide by zero.
The second problem is your use of Enter-PSSession, which is used exclusively in interactive sessions. For an unattended script you would use Invoke-Command instead, however, in this case we could also rely on Get-CimInstance -ComputerName to query the remote computers (note that this operation is performed in parallel and does not require a loop over the $serversToScan array).
$ServersToScan = 'x, y, z'
#Threshold Definition %
$Critical = 0.5
$Warning = 0.15
$params = #{
ClassName = 'Win32_LogicalDisk'
Filter = "DriveType = '3'"
ComputerName = $ServersToScan
}
$finalReport = Get-CimInstance #params | ForEach-Object {
$free = $_.FreeSpace / $_.Size
$status = switch($free) {
{ $_ -gt $Warning } { 'Normal'; break }
{ $_ -gt $Critical } { 'Warning'; break }
Default { 'Critical' }
}
[pscustomobject]#{
'Computer Name' = $_.PSComputerName
'Drive Letter' = $_.DeviceID
'Drive Name' = $_.VolumeName
'Total Space (GB)' = $_.Size / 1Gb
'Free Space (GB)' = $_.FreeSpace / 1Gb
'Free Space (%)' = $free.ToString('P0')
'Status' = $status
}
}

PowerShell: Sort Drive Encryption Status for Custom Output

I'm trying to get the encryption status of all drives on a Windows system and sort that list in a custom formatted output. I need this because the output is going to a Nagios server; it messes up the formatting of the standard output for Get-BitLockerVolume and is too long.
Here's what I have so far. I'm trying to sort the output in such a manner that the system drive is listed first and gives the mount point (drive letter) along with the percentage.
[array]$DriveTypes = Get-BitLockerVolume | Sort-Object VolumeType | Select-Object VolumeType
[array]$DriveMounts = Get-BitLockerVolume | Sort-Object VolumeType | Select-Object MountPoint
[array]$WDEPercent = Get-BitLockerVolume | Sort-Object VolumeType | Select-Object EncryptionPercentage
for ($i = 0; $i -lt $DriveTypes.Count; $i++) {
if ($DriveIndex -eq $DriveTypes.Count) {
$TextDriveListing = $TextDriveListing + $DriveMounts.MountPoint+" ("+$DriveTypes.VolumeType+") at "+$WDEPercent.EncryptionPercentage+"%."
}
else {
$TextDriveListing = $TextDriveListing + $DriveMounts.MountPoint+" ("+$DriveTypes.VolumeType+") at "+$WDEPercent.EncryptionPercentage+"%, "
}
if ($WDEPercent.EncryptionPercentage -lt $ReqValue) {
$NoEncryptFlag = 1
}
}
My desired output, for example, is this:
C: (OperatingSystem) at 100%, D: (Data) at 0%.
What I actually end up with is this:
C: D: (OperatingSystem Data) at 100 0%, C: D: (OperatingSystem Data) at 100 0%,
I did try something deriving from an answer to "How to sort a Multi Dimensional Array in Powershell" to test it out, commenting out my aforementioned for block and putting in:
$ListDrives | ForEach-Object {
Get-BitLockerVolume #{
MountPoint = $_[0]
EncryptionPercentage = $_[1]
}
} | Sort-Object VolumeType
Write-Host $ListDrives
That spit out this error:
Cannot index into a null array.
At C:****************.ps1:142 char:3
Get-BitLockerVolume #{
~~~~~~~~~~~~~~~~~~~~~~
CategoryInfo : InvalidOperation: (:) [], RuntimeException
FullyQualifiedErrorId : NullArray
What am I doing wrong? Any suggestions?
Thanks so much in advance!
Try this:
for ($i = 0; $i -lt $DriveTypes.Count; $i++) {
if ($i -eq ($DriveTypes.Count - 1)) {
$TextDriveListing = $TextDriveListing + $DriveMounts[$i].MountPoint+" ("+$DriveTypes[$i].VolumeType+") at "+$WDEPercent[$i].EncryptionPercentage+"%."
}
else {
$TextDriveListing = $TextDriveListing + $DriveMounts[$i].MountPoint+" ("+$DriveTypes[$i].VolumeType+") at "+$WDEPercent[$i].EncryptionPercentage+"%, "
}
if ($WDEPercent[$i].EncryptionPercentage -lt $ReqValue) {
$NoEncryptFlag = 1
}
}
You weren't using the $i from your For Loop to access specific indexes in your collections (i've added [$i] to each of your collection variables to do so). You were also using a variable called $DriveIndex that was never populated and I think this needed to be comparing to $i also, however the logic was also one that would never be true because the For loop would end before it was so (so i've changed the logic to ($i -eq ($DriveTypes.Count - 1)).
Here's a tidier version that I think also gets you the same result:
$TextDriveListing = ''
$Drives = Get-BitLockerVolume | Sort-Object VolumeType | Select VolumeType,MountPoint,EncryptionPercentage
$Drives | ForEach-Object {
$TextDriveListing += "$($_.MountPoint) ($($_.VolumeType)) at $($_.EncryptionPercentage)%,"
If ($_.EncryptionPercentage -lt $ReqValue) { $NoEncryptFlag = 1 }
} -End { $TextDriveListing -Replace ',$','.' }
Uses a single variable for the three properties you wanted to access, rather than putting them in to separate variables which was unnecessary.
Uses a ForEach-Object loop to access each item (and their properties) in that collection via the special token $_.
Uses a single double quoted string for output, with the object/properties accessed via the subexpression operator $().
Puts a comma on the end of each line, but then at the End of the ForEach, uses regex to replace the comma at the end of the line (regex: $ token) with a full stop.
Both sets of code are untested, so may need tweaking.

Powershell match properties and then selectively combine objects to create a third

I have a solution for this but I believe it is not the best method as it takes forever so I am looking for a faster/better/smarter way.
I have multiple pscustomObject objects pulled from .csv files. Each object has at least one common property. One is relatively small (around 200-300 items/lines in the object) but the other is sizable (around 60,000-100,000 items). The contents of one may or may not match the contents of the other.
I need to find where the two objects match on a specific property and then combine the properties of each object into one object with all or most properties.
An example snippet of the code (not exact but for this it should work - see the image for the sample data):
DataTables
Write-Verbose "Pulling basic Fruit data together"
$Purchase = import-csv "C:\Purchase.csv"
$Selling = import-csv "C:\Selling.csv"
Write-Verbose "Combining Fruit names and removing duplicates"
$Fruits = $Purchase.Fruit
$Fruits += $Selling.Fruit
$Fruits = $Fruits | Sort-Object -Unique
$compareData = #()
Foreach ($Fruit in $Fruits) {
$IndResults = #()
$IndResults = [pscustomobject]#{
#Adding Purchase and Selling data
Farmer = $Purchase.Where({$PSItem.Fruit -eq $Fruit}).Farmer
Region = $Purchase.Where({$PSItem.Fruit -eq $Fruit}).Region
Water = $Purchase.Where({$PSItem.Fruit -eq $Fruit}).Water
Market = $Selling.Where({$PSItem.Fruit -eq $Fruit}).Market
Cost = $Selling.Where({$PSItem.Fruit -eq $Fruit}).Cost
Tax = $Selling.Where({$PSItem.Fruit -eq $Fruit}).Tax
}
Write-Verbose "Loading Individual results into response"
$CompareData += $IndResults
}
Write-Output $CompareData
I believe the issue is in lines like these:
Farmer = $Purchase.Where({$PSItem.Fruit -eq $Fruit}).Farmer
If I understand this it is looking through the $Purchase object each time it goes through this line. I am looking for a way to speed that whole process up instead of having it look through the entire object for each match attempt.
Using this Join-Object:
$Purchase | Join $Selling -On Fruit | Format-Table
Result (using Simon Catlin's data):
Fruit Farmer Region Water Market Cost Tax
----- ------ ------ ----- ------ ---- ---
Apple Adam Alabama 1 MarketA 10 0.1
Cherry Charlie Cincinnati 2 MarketC 20 0.2
Damson Daniel Derby 3 MarketD 30 0.3
Elderberry Emma Eastbourne 4 MarketE 40 0.4
Fig Freda Florida 5 MarketF 50 0.5
using Join-Object
http://ramblingcookiemonster.github.io/Join-Object/
Join-Object -Left $purchase -Right $selling -LeftJoinProperty fruit -RightJoinProperty fruit -Type OnlyIfInBoth | ft
I had this very problem when trying to consolidate employee data from our HR system against employee data in our AD forest. With many thousands of rows, the process was taking an age.
I eventually walked away from custom objects and reverted to old school hash tables.
The hash tables entries themselves then held a sub-hash table with the data. In your instance, the outer hash would be keyed on $fruit, with the sub-hash containing the various attributes, e.g.: farmer, region, Etc.
Hash tables are lightning quick in comparison. It's a shame that PowerShell is slow in this regard.
Shout if you need more info.
26/01 Example code... assuming I'm correctly understanding the requirement:
PURCHASE.CSV:
Fruit,Farmer,Region,Water
Apple,Adam,Alabama,1
Cherry,Charlie,Cincinnati,2
Damson,Daniel,Derby,3
Elderberry,Emma,Eastbourne,4
Fig,Freda,Florida,5
SELLING.CSV
Fruit,Market,Cost,Tax
Apple,MarketA,10,0.1
Cherry,MarketC,20,0.2
Damson,MarketD,30,0.3
Elderberry,MarketE,40,0.4
Fig,MarketF,50,0.5
CODE
[String] $Local:strPurchaseFile = 'c:\temp\purchase.csv';
[String] $Local:strSellingFile = 'c:\temp\selling.csv';
[HashTable] $Local:objFruitHash = #{};
[System.Array] $Local:objSelectStringHit = $null;
[String] $Local:strFruit = '';
if ( (Test-Path -LiteralPath $strPurchaseFile -PathType Leaf) -and (Test-Path -LiteralPath $strSellingFile -PathType Leaf) ) {
#
# Populate data from purchase file.
#
foreach ( $objSelectStringHit in (Select-String -LiteralPath $strPurchaseFile -Pattern '^([^,]+),([^,]+),([^,]+),([^,]+)$' | Select-Object -Skip 1) ) {
$objFruitHash[ $objSelectStringHit.Matches[0].Groups[1].Value ] = #{ 'Farmer' = $objSelectStringHit.Matches[0].Groups[2].Value;
'Region' = $objSelectStringHit.Matches[0].Groups[3].Value;
'Water' = $objSelectStringHit.Matches[0].Groups[4].Value;
};
} #foreach-purchase-row
#
# Populate data from selling file.
#
foreach ( $objSelectStringHit in (Select-String -LiteralPath $strSellingFile -Pattern '^([^,]+),([^,]+),([^,]+),([^,]+)$' | Select-Object -Skip 1) ) {
$objFruitHash[ $objSelectStringHit.Matches[0].Groups[1].Value ] += #{ 'Market' = $objSelectStringHit.Matches[0].Groups[2].Value;
'Cost' = [Convert]::ToDecimal( $objSelectStringHit.Matches[0].Groups[3].Value );
'Tax' = [Convert]::ToDecimal( $objSelectStringHit.Matches[0].Groups[4].Value );
};
} #foreach-selling-row
#
# Output data. At this point, you could now build a PSCustomObject.
#
foreach ( $strFruit in ($objFruitHash.Keys | Sort-Object) ) {
Write-Host -Object ( '{0,-15}{1,-15}{2,-15}{3,-10}{4,-10}{5,10:C}{6,10:P}' -f
$strFruit,
$objFruitHash[$strFruit]['Farmer'],
$objFruitHash[$strFruit]['Region'],
$objFruitHash[$strFruit]['Water'],
$objFruitHash[$strFruit]['Market'],
$objFruitHash[$strFruit]['Cost'],
$objFruitHash[$strFruit]['Tax']
);
} #foreach
} else {
Write-Error -Message 'File error.';
} #else-if
I needed to do this myself for something similar. I wanted to take two system array objects and compare them pulling out the matches without having to manipulate the input data each time. Here's the method I used, which although I appreciate this is inefficient, it was instantaneous for the 200 or so records I had to work with.
I tried to translate what I was doing (users and their old and new home directories) into farmers, fruit and markets etc so I hope it makes sense!
$Purchase = import-csv "C:\Purchase.csv"
$Selling = import-csv "C:\Selling.csv"
$compareData = #()
foreach ($iPurch in $Purchase) {
foreach ($iSell in $Selling) {
if ($iPurch.fruit -match $iSell.fruit) {
write-host "Match found between $($iPurch.Fruit) and $($iSell.Fruit)"
$hash = #{
Fruit = $iPurch.Fruit
Farmer = $iPurch.Farmer
Region = $iPurch.Region
Water = $iPurch.Water
Market = $iSell.Market
Cost = $iSell.Cost
Tax = $iSell.Tax
}
$Build = New-Object PSObject -Property $hash
$Total = $Total + 1
$compareData += $Build
}
}
}
Write-Host "Processed $Total records"

How to store outlook email body in array - Powershell?

the script below reads my outlook emails but how do I access the output. I'm new too Powershell and I'm still getting used to certain things. I just want to get the body of 10 unread outlook emails and store them in an Array called $Body.
$olFolderInbox = 6
$outlook = new-object -com outlook.application;
$ns = $outlook.GetNameSpace("MAPI");
$inbox = $ns.GetDefaultFolder($olFolderInbox)
#checks 10 newest messages
$inbox.items | select -first 10 | foreach {
if($_.unread -eq $True) {
$mBody = $_.body
#Splits the line before any previous replies are loaded
$mBodySplit = $mBody -split "From:"
#Assigns only the first message in the chain
$mBodyLeft = $mbodySplit[0]
#build a string using the –f operator
$q = "From: " + $_.SenderName + ("`n") + " Message: " + $mBodyLeft
#create the COM object and invoke the Speak() method
(New-Object -ComObject SAPI.SPVoice).Speak($q) | Out-Null
}
}
This may not be a factor here, since you're looping through only ten elements, but using += to add elements to an array is very slow.
Another approach would be to output each element within the loop, and assign the results of the loop to $body. Here's a simplified example, assuming that you want $_.body:
$body = $inbox.items | select -first 10 | foreach {
if($_.unread -eq $True) {
$_.body
}
}
This works because anything that is output during the loop will be assigned to $body. And it can be much faster than using +=. You can verify this for yourself. Compare the two methods of creating an array with 10,000 elements:
Measure-Command {
$arr = #()
1..10000 | % {
$arr += $_
}
}
On my system, this takes just over 14 seconds.
Measure-Command {
$arr = 1..10000 | % {
$_
}
}
On my system, this takes 0.97 seconds, which makes it over 14 times faster. Again, probably not a factor if you are just looping through 10 items, but something to keep in mind if you ever need to create larger arrays.
define $body = #(); before your loop
Then just use += to add the elements
Here's another way:
$body = $inbox.Items.Restrict('[Unread]=true') | Select-Object -First 10 -ExpandProperty Body

Resources