Set-S3Acl Powershell cmdlet not working post 20-30k objects - powershell

My client had an issue, i.e., they accidentally copied 13 million Objects (files) to S3 bucket with wrong permissions. They have asked my team to fix it. We have to update each 13 million files in the S3 bucket with correct ACLs. We are using below powershell script to fix it. However, when the script runs on a folder with more than 20-30k objects, it fails to set the ACLs. [It iterates thru the loop, but it wont set the permission post 20-30k objects, no exception either]
I am suspecting that the requests might be getting throttled. Have any one of you came across such issue. Please help me on how to proceed.
I am looking for answers for the below questions:
1. If the API calls are getting throttled # 20-30k objects, how can I modify my script to overcome it.
2. What is the best practice in terms of scripting to "modify" AWS resources (like set ACL permission to S3 objects) for millions of objects
(I am not looking for the "BucketPolicy" approach, as we have to do it with a script and apply the ACLs to every S3 object)
Param (
[Parameter(Position=0,Mandatory=$true)]
[string]$profile,
[Parameter(Position=1,Mandatory=$true)]
[string]$switchToAccount,
[Parameter(Position=2,Mandatory=$true)]
[string]$roleName,
[Parameter(Position=3,Mandatory=$true)]
[string]$keyPrefix
)
#Set base AWS credentials
Set-AWSCredentials -ProfileName $profile
Set-DefaultAWSRegion -Region $region
#Get and set MFA device ARN
$userName = (Get-IAMUser).UserName
$mfaArn = "arn:aws:iam::xxxxxxxxx:mfa/" + "$userName"
#Configure CAA roles
$roleArn = "arn:aws:iam::" + "$switchToAccount" + ":role/" + "$roleName"
$roleSessionName = "xxxxxxxxxxxx"
#Prompt for MFA token and perform CAA request
$tokenCode = Read-Host -Prompt "Enter MFA token for $accountNumber"
$switchRole = Use-STSRole -RoleSessionName $roleSessionName -RoleArn $roleArn -TokenCode $tokenCode -SerialNumber $mfaArn
#Set new role for CAA
Set-AWSCredentials -Credential $switchRole.Credentials
#Declare access level for S3 Object ACL grantees
$FULL_CONTROL = [Amazon.S3.S3Permission]::FULL_CONTROL
$grants = #();
#Grant FULL_CONTROL access to xxxxxxxxxxxxxxxxxxxxx
$grantee1 = New-Object -TypeName Amazon.S3.Model.S3Grantee
$grantee1.EmailAddress = "xxxxxxxxxxxxxxxxxxx"
#Grant FULL_CONTROL access to xxxxxxxxxxxxxxxxx
$grantee2 = New-Object -TypeName Amazon.S3.Model.S3Grantee
$grantee2.EmailAddress = "xxxxxxxxxxxxxxxxxxx"
#Grant FULL_CONTROL access to xxxxxxxxxxxxxxxxxxxx
$grantee3 = New-Object -TypeName Amazon.S3.Model.S3Grantee
$grantee3.EmailAddress = "xxxxxxxxxxxxxxxxxxxxx"
#Create grant and add to grant list
$grant1 = New-Object -TypeName Amazon.S3.Model.S3Grant
$grant1.Grantee = $grantee1
$grant1.Permission = $FULL_CONTROL
$grants += $grant1
#Create grant and add to grant list
$grant2 = New-Object -TypeName Amazon.S3.Model.S3Grant
$grant2.Grantee = $grantee2
$grant2.Permission = $FULL_CONTROL
$grants += $grant2
#Create grant and add to grant list
$grant3 = New-Object -TypeName Amazon.S3.Model.S3Grant
$grant3.Grantee = $grantee3
$grant3.Permission = $FULL_CONTROL
$grants += $grant3
#Set bucket name for S3 objects
$bucketName = "xxxxxxxxxxxxxxxxxxxxxxxxx"
#Get all S3 Objects in specified bucket
$s3Objects = Get-S3Object -BucketName $bucketName -KeyPrefix $keyPrefix
#Count for progress bar
$totalObjects = $s3Objects.length
$i = 1
$fail_count = 0
$current_count = 0
$file_path = "C:\Users\Administrator\Desktop\Failed_Objects_new\" + $keyPrefix.Replace("/","_") + ".txt"
$file_path_retry = "C:\Users\Administrator\Desktop\Failed_Objects_new_retry\" + $keyPrefix.Replace("/","_") + ".txt"
new-item $file_path -ItemType file
new-item $file_path_retry -ItemType file
"Total Object Count:" + $totalObjects + "`n" | Out-File $file_path -Append
foreach($s3Object in $s3Objects){
$owner = $s3Object.owner.id
$s3Object.name | Write-Output
$current_count++
#Extracts Key for each S3 object in bucket
$key = $s3Object.Key
#Logging
Write-Host "Setting $bucketName | $key | $grants"
# Pick objects that were modified on or before July 15th
try {
if (($s3Object.LastModified.month -lt 7)) {
Set-S3ACL -BucketName $bucketName -Key $key -Grant $grants -OwnerId $owner
$owner | Write-Host
}
elseif(($s3Object.LastModified.month -eq 7) -and ($s3Object.LastModified.day -le 15)) {
Set-S3ACL -BucketName $bucketName -Key $key -Grant $grants -OwnerId $owner
$owner | Write-Host
}
}catch{
"Failed $bucketName | $key | $grants" | out-file $file_path -Append
$key | Out-File $file_path_retry -Append
$fail_count++
}
Write-Host "progress: " $current_count "/" $totalObjects
#Update progress bar
$percentComplete = $i/$totalObjects
Write-Progress -Activity "Setting S3 Object ACL's" -Status "$i% complete" -PercentComplete $percentComplete
$i++
}
"`n`n Total Fail Count:" + $fail_count | Out-File $file_path -Append

Steps to debug the problem:
Make sure if it is throttling issue. In for loop; break after 10k objects and see if everything works fine.
Also, put print statements inside try block both if and else.. to make sure if its reaching there or not; and when is it failing.

Related

Set a variable equal to the output of a powershell script

This is a little difficult to explain, but I will do my best. I am writing some code to import AD contacts to users' mailboxes through EWS using Powershell.
I have a Main.ps1 file that calls all the other scripts that do work in the background (for example 1 imports the AD modules) another imports O365 modules.
I have 1 script container that connect to EWS. The code looks like this:
#CONFIGURE ADMIN CREDENTIALS
$userUPN = "User#domain.com"
$AESKeyFilePath = ($pwd.ProviderPath) + "\ConnectToEWS\aeskey.txt"
$SecurePwdFilePath = ($pwd.ProviderPath) + "\ConnectToEWS\password.txt"
$AESKey = Get-Content -Path $AESKeyFilePath -Force
$securePass = Get-Content -Path $SecurePwdFilePath -Force | ConvertTo-SecureString -Key $AESKey
#create a new psCredential object with required username and password
$adminCreds = New-Object System.Management.Automation.PSCredential($userUPN, $securePass)
Try
{
[Reflection.Assembly]::LoadFile("\\MBX-Server\c$\Program Files\Microsoft\Exchange\Web Services\2.2\Microsoft.Exchange.WebServices.dll") | Out-Null
$service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService([Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2013_SP1)
$service.Credentials = New-Object Microsoft.Exchange.WebServices.Data.WebCredentials($userUPN,$adminCreds.GetNetworkCredential().Password)
$service.Url = new-object Uri("https://outlook.office365.com/EWS/Exchange.asmx");
return $service
}
Catch
{
Write-Output "Unable to connect to EWS. Make sure the path to the DLL or URL is correct"
}
The output of that code prints out the Service connection, but I want the information for that output stored in a variable such as $service.
Then I would pass that variable to another script that binds to the mailbox I want...
The problem I am having is $service doesn't seem to be storing that information. It only print it out once when I return it from the script above, but it doesn't append that information in the main script. When I print out $service it prints out once, but then it clears itself.
Here is my main script
CLS
#Root Path
$rootPath = $pwd.ProviderPath #$PSScriptRoot #$pwd.ProviderPath
Write-Host "Importing all necessary modules."
#******************************************************************
# PREREQUISITES
#******************************************************************
#Nuget - Needed for O365 Module to work properly
if(!(Get-Module -ListAvailable -Name NuGet))
{
#Install NuGet (Prerequisite) first
Install-PackageProvider -Name NuGet -Scope CurrentUser -Force -Confirm:$False
}
#******************************************************************
#Connect w\ Active Directory Module
& $rootPath\AD-Module\AD-module.ps1
#Load the O365 Module
& $rootPath\O365-Module\O365-module.ps1
#Clear screen after loading all the modules/sessions
CLS
#******************************************************************
# PUT CODE BELOW
#******************************************************************
#GLOBAL VARIABLES
$global:FolderName = $MailboxToConnect = $Service = $NULL
#Connect to EWS
& $rootPath\ConnectToEWS\ConnectToEWS.ps1
#Debug
$Service
#Create the Contacts Folder
& $rootPath\CreateContactsFolder\CreateContactsFolder.ps1
#Debug
$service
$ContactsFolder
#Clean up Sessions after use
if($NULL -ne (Get-PSSession))
{
Remove-PSSession *
}
[GC]::Collect()
The first time I output the $service variable, it prints fine. In the 2nd Debug output it doesn't print out anymore, and I believe that it why the script is failing when I launch "CreateContactsFolder.ps1"
Here is the content of "CreateContactsFolder.ps1"
CLS
Try
{
$service.ImpersonatedUserId = New-Object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $MailboxToConnect);
$RootFolder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,[Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::MsgFolderRoot)
$RootFolder.Load()
#Check to see if they have a contacts folder that we want
$FolderView = new-Object Microsoft.Exchange.WebServices.Data.FolderView(1000)
$ContactsFolderSearch = $RootFolder.FindFolders($FolderView) | Where-Object {$_.DisplayName -eq $FolderName}
if($ContactsFolderSearch)
{
$ContactsFolder = [Microsoft.Exchange.WebServices.Data.ContactsFolder]::Bind($service,$ContactsFolderSearch.Id);
#If folder exists, connect to it. Clear existing Contacts, and reupload new (UPDATED) Contact Info
Write-Output "Folder alreads exists. We will remove all contacts under this folder."
# Attempt to empty the target folder up to 10 times.
$tries = 0
$max_tries = 0
while ($tries -lt 2)
{
try
{
$tries++
$ErrorActionPreference='Stop'
$ContactsFolder.Empty([Microsoft.Exchange.WebServices.Data.DeleteMode]::HardDelete, $true)
$tries++
}
catch
{
$ErrorActionPreference='SilentlyContinue'
$rnd = Get-Random -Minimum 1 -Maximum 10
Start-Sleep -Seconds $rnd
$tries = $tries - 1
$max_tries++
if ($max_tries -gt 100)
{
Write-Output "Error; Cannot empty the target folder; `t$EmailAddress"
}
}
}
}
else
{
#Contact Folder doesn't exist. Let's create it
try
{
Write-Output "Creating new Contacts Folder called $FolderName"
$ContactsFolder = New-Object Microsoft.Exchange.WebServices.Data.ContactsFolder($service);
$ContactsFolder.DisplayName = $FolderName
$ContactsFolder.Save([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::MsgFolderRoot)
}
catch
{
Write-Output "Error; Cannot create the target folder; `t$EmailAddress"
}
}
}
Catch
{
Write-Output "Couldn't connect to the user's mailbox. Make sure the admin account you're using to connect to has App Impersonization permissions"
Write-Output "Check this link for more info: https://help.bittitan.com/hc/en-us/articles/115008098447-The-account-does-not-have-permission-to-impersonate-the-requested-user"
}
return $ContactsFolder
In the Main script, capture the returned variable from the EWS script like
$service = & $rootPath\ConnectToEWS\ConnectToEWS.ps1
Or dot-source that script into the Main script, so the variables from EWS.ps1 are local to the Main script, so you don't need to do return $service in there:
. $rootPath\ConnectToEWS\ConnectToEWS.ps1
and do the same for the CreateContactsFolder.ps1 script
OR
define the important variables in the called scripts with a global scope $global:service and $global:ContactsFolder
See About_Scopes

PowerShell Script uploading file to SharePoint doesn't work when run from Task Scheduler

Overview: I have a script which runs a query to extract all Audit Logs from the past day, from a SharePoint site. It then creates a .csv file, and uploads it to the "Shared Documents" folder on SharePoint.
The script works perfectly when I manually run it from PowerShell console, regardless if it's run with admin rights, or not.
NOTE: This SharePoint solution is On-Premise, not Online.
The Issue: When I run the script from Task Scheduler it generates the .csv file, and completes the whole script, but the file is never uploaded. There's no error messages.
Task Scheduler Settings: I use "Run whether user is logged on or not" and "Run with highest privileges"
Here's the full script:
Start-Transcript -Path "C:\Timer Jobs\exampleDomain.Audit.CreateAndTrimLog\transcript17.txt" -NoClobber
Add-PSSnapin "Microsoft.SharePoint.PowerShell"
#Get date, and format it. Used for file name, and for deleting old Audit files
$today = Get-Date
$startDate = $today.AddDays(-1)
$todayFormatted = $today.ToString('dd-MM/yyyy_HH-mm-ss')
#Get site and save siteURL
$siteUrl = "https://example.com"
$docLibName = "Shared Documents"
#site is needed for Audit Query
$site = Get-SPSite -Identity $siteUrl
#web is needed to upload the file
$web = Get-SPWeb -Identity $siteUrl
$list = $web.Lists[$docLibName]
$folder = $list.RootFolder
$files = $folder.Files
#Creaty query for Audits
$wssQuery = New-Object -TypeName Microsoft.SharePoint.SPAuditQuery($site)
$wssQuery.SetRangeStart($startDate)
$wssQuery.SetRangeEnd($today)
#Create Audit Collection object
$auditCol = $site.Audit.GetEntries($wssQuery)
#Get all users on site
$users = $site.RootWeb.SiteUsers
#Get and add $userName to each $audit
#Ignore when it says "User cannot be found" in log
$CachedUsers = #{};
foreach($audit in $auditCol){
$curUserId = $audit.UserId;
$user = $null;
if($CachedUsers.$curUserId -eq $null){
$user = $users.GetById($curUserId);
$CachedUsers.$curUserUI = $user;
} else {
$user = $CachedUsers.$curUserId;
}
$userName = ($user.DisplayName + " <" + $user.LoginName) + ">";
$audit | Add-Member NoteProperty -Name "UserName" -Value $userName -Force;
}
#Export CSV-file. Save fileName and filePath for when uploading to SharePoint
$fileName = ("domain_Audit_Log_" + $todayFormatted + ".csv")
$filePath = "C:\Users\xml\" + ($fileName)
$auditCol | Export-Csv -Append -path ($filePath) -NoTypeInformation -Delimiter ";" -Encoding UTF8
$file = Get-ChildItem $filePath
[Microsoft.SharePoint.SPFile]$spFile = $web.GetFile("/" + $folder.Url + "/" + $file.Name)
if($spFile.Exists -eq $false) {
#Open FileStream
$fileStream = ([System.IO.FileInfo] (Get-Item $file.FullName)).OpenRead()
#Add File
Write-Host "Uploading file" $file.Name "to" $folder.ServerRelativeUrl "..."
try {
[Microsoft.SharePoint.SPFile]$spFile = $files.Add($folder.Url + "/" + $file.Name, [System.IO.Stream]$fileStream, $true)
Write-Host "Success"
} catch {
Write-Host "Error"
}
#Close FileStream
$fileStream.Close()
}
$web.Dispose()
#$site.Audit.DeleteEntries($startDate)
Only difference in the Transcript.txt file, when I run it Manually vs. Task Scheduler is these lines, that are added in the Task Scheduler Tanscript:
PS>$global:?
True
The Transcript prints the "Success" line from my Try-Catch
Problem was in SharePoint. Whenever I uploaded a file using Task Scheduler the file was marked as "Checked Out", so it was not viewable for other users.

Powershell progress bar for invoke-sqlcmd

So im trying to make a backup script that will download a csv from my mssql, then zip the file, then upload the backup to amazon S3.
The issue im having is the table is 20Million lines on average when i run the script daily. and it looks like it just lags forever untill it completes like 20 minutes later. I was wondering if there is a way to show a progress bar for the invoke-sqlcmd specificly. ive done some research and all the examples i could find is to make a progress bar on a for loop only, not for a single commands progress.
Here is my code:
ECHO "Starting Download"
Import-Module sqlps
#$SQLquery="SELECT * FROM dbo.$PREFIX$i"
$SQLquery="SELECT * FROM dbo.events"
ECHO "Executing query = $SQLquery"
$hostname = "."
$pass = "test"
$usern = "test"
$database = "theDB"
$result=invoke-sqlcmd -ServerInstance $hostname -query $SQLquery -HostName $hostname -Password $pass -Username $usern -Database $database -verbose
#echo $result
pause
$result |export-csv -path $CSVPATH -notypeinformation
pause
ECHO "Starting Zip:"
Compress-Archive -LiteralPath $CSVPATH -CompressionLevel Optimal -DestinationPath $ZIPPATH
ECHO "Starting Delete: $CSVPATH "
del "$CSVPATH"
echo "Removed $CSVNAME"
aws s3 cp $ZIPPATH s3://test_$ZIPNAME
pause
this script works but as i said i would like to add a progress bar to the invoke-sqlcmd so that it doesnt look like its frozen while it downloads the huge file.
this is what i could find so far but this only works for a loops progression
$VerbosePreference = "Continue"
Write-Verbose "Test Message"
for ($a=1; $a -lt 100; $a++) {
Write-Progress -Activity "Working..." -PercentComplete $a -CurrentOperation "$a% complete" -Status "Please wait."
Start-Sleep -Milliseconds 100
}
Considering your huge ~20 million record data set, it's probably a good idea to use some of the .NET classes in the System.Data.Common namespace. And I'm not sure about how Export-Csv is implemented, but System.IO.StreamWriter is very efficient for writing large files.
A simple tested/working example with inline comments:
# replace $tableName with yours
$sqlCount = "SELECT COUNT(*) FROM dbo.$($tableName)";
$sqlSelect = "SELECT * FROM dbo.$($tableName)";
$provider = [System.Data.Common.DbProviderFactories]::GetFactory('System.Data.SqlClient');
$connection = $provider.CreateConnection();
# replace $connectionString with yours, e.g.:
# "Data Source=$($INSTANCE-NAME);Initial Catalog=$($DATABASE-NAME);Integrated Security=True;";
$connection.ConnectionString = $connectionString;
$command = $connection.CreateCommand();
# get total record count for Write-Progress
$command.CommandText = $sqlCount;
$connection.Open();
$reader = $command.ExecuteReader();
$totalRecords = 0;
while ($reader.Read()) {
$totalRecords = $reader[0];
}
$reader.Dispose();
# select CSV data
$command.CommandText = $sqlSelect;
$reader = $command.ExecuteReader();
# get CSV field names
$columnNames = #();
for ($i = 0; $i -lt $reader.FieldCount; $i++) {
$columnNames += $reader.GetName($i);
}
# read and populate data one row at a time
$values = New-Object object[] $columnNames.Length;
$currentCount = 0;
# replace $CSVPATH with yours
$writer = New-Object System.IO.StreamWriter($CSVPATH);
$writer.WriteLine(($columnNames -join ','));
while ($reader.Read()) {
$null = $reader.GetValues($values);
$writer.WriteLine(($values -join ','));
if (++$currentCount % 1000 -eq 0) {
Write-Progress -Activity 'Reading data' `
-Status "Finished reading $currentCount out of $totalRecords records." `
-PercentComplete ($currentCount / $totalRecords * 100);
}
}
$command.Dispose();
$reader.Dispose();
$connection.Dispose();
$writer.Dispose();

SharePoint Online Powershell Folder creation

I need to be able to script folder creation from a csv into a SharePoint Online document library with each folder with permission inheritance disabled and for different user to each folder to be added.
The following code can create the folders and disable the inheritance but it seems to try add a group but not a user. How to make it add a user instead?
Thanks.
### Get the user credentials
$credential = Get-Credential
$username = $credential.UserName
$password = $credential.GetNetworkCredential().Password
$securePassword = ConvertTo-SecureString $password -AsPlainText -Force
### Input Parameters
$url = 'URL HERE'
$csvfilepath='C:\Scripts\data.csv'
$libname ='BUS61'
### References
# Specified the paths where the dll's are located.
Add-Type -Path 'C:\Scripts\SPOCmdlets\Microsoft.SharePoint.Client.dll'
Add-Type -Path 'C:\Scripts\SPOCmdlets\Microsoft.SharePoint.Client.Runtime.dll'
### CreateFolder with Permissions Function
function CreateFolderWithPermissions()
{
# Connect to SharePoint Online and get ClientContext object.
$clientContext = New-Object Microsoft.SharePoint.Client.ClientContext($url)
$credentials = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($username, $securePassword)
$clientContext.Credentials = $credentials
Function GetRole
{
[CmdletBinding()]
param
(
[Parameter(Mandatory = $true, Position = 1)]
[Microsoft.SharePoint.Client.RoleType]$rType
)
$web = $clientContext.Web
if ($web -ne $null)
{
$roleDefs = $web.RoleDefinitions
$clientContext.Load($roleDefs)
$clientContext.ExecuteQuery()
$roleDef = $roleDefs | Where-Object { $_.RoleTypeKind -eq $rType }
return $roleDef
}
return $null
}
# Get the SharePoint web
$web = $clientContext.Web;
$clientContext.Load($web)
#Get the groups
$groups = $web.SiteGroups
$clientContext.Load($groups)
$clientContext.ExecuteQuery()
#Read CSV File and iterate
$csv = Import-CSV $csvfilepath
foreach ($row in $csv)
{
#Create Folder
$folder = $web.Folders.Add($libname + "/" + $row.Folder)
$clientContext.Load($folder)
$clientContext.ExecuteQuery()
#Assign Role
$group = $groups.GetByName($row.Group)
$clientContext.Load($group)
$clientContext.ExecuteQuery()
$roleType= $row.Role
$roleTypeObject = [Microsoft.SharePoint.Client.RoleType]$roleType
$roleObj = GetRole $roleTypeObject
$usrRDBC = $null
$usrRDBC = New-Object Microsoft.SharePoint.Client.RoleDefinitionBindingCollection($clientContext)
$usrRDBC.Add($roleObj)
# Remove inherited permissions
$folder.ListItemAllFields.BreakRoleInheritance($false, $true)
$clientContext.Load($folder.ListItemAllFields.RoleAssignments.Add($group, $usrRDBC))
$folder.Update()
$clientContext.ExecuteQuery()
# Display the folder name and permission
Write-Host -ForegroundColor Blue 'Folder Name: ' $folder.Name ' Group: '$row.Group ' Role: ' $roleType;
}
}
#Execute the function
CreateFolderWithPermissions
Let's assume that you will define user login in your CSv file. Than you have to change the line:
$group = $groups.GetByName($row.Group)
to
$user = $web.EnsureUser($row.User)
and replace all references to $group variable with $user
More generic approach for searching for a user (with for example display name) would be using Utility.ResolvePrincipal method:
[Microsoft.SharePoint.Client.Utilities.Utility]::ResolvePrincipal($clientContext, $web, "DisplayName", ([Microsoft.SharePoint.Client.Utilities.PrincipalType]::User), ([Microsoft.SharePoint.Client.Utilities.PrincipalSource]::All), $null, $false)

EWS Powershell Exchange 2013 FindFolders returns 0 results

I am currently trying to make this script run on exchange 2013 to convert folder types from IPF.IMAP to IPF.NOTE as the folders are not showing on mobile devices after being imported from Imap. This script returns 0 results after running and multiple Doesnt Exist. If I output the folder names they are coming through, so i am not sure why the FindFolders is not returning any results.
I tried turning on impersonation (commented out here) but get an error saying I do not have permissions to impersonate even though I am logged in as administrator and running on powershell as admin. I am not sure if this is even necessary as the script works fine and returns the folder names for both $mbxfolder.Name and $SfSearchFilter, but only until it hits the FindFolders line, then the TotalCount is always 0.
Import-Module -Name "C:\Program Files\Microsoft\Exchange\Web Services\1.2\Microsoft.Exchange.WebServices.dll"
$exchService = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService
$exchService.UseDefaultCredentials = $true
$exchService.AutodiscoverUrl('email#domain.com', {$true})
$MBXID = "email#domain.com" #Define mailboxID
foreach ($MailboxIdentity in $MBXID) {
Write-Host "Searching for $MailboxIdentity"
$mailbox = (Get-Mailbox -Identity $MailboxIdentity)
$MailboxName = (Get-Mailbox -Identity $MailboxIdentity).PrimarySmtpAddress.ToString()
$MailboxRootid = new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Root,$MailboxName) #MsgFolderRoot selection and creation of new root
$MailboxRoot = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($exchService,$MailboxRootid)
#$exchService.ImpersonatedUserId = New-Object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId -ArgumentList ([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress),$MailboxName #Define impersonation
$folderid = $MailboxRootid
$f1 = $MailboxRoot
$fold = get-mailboxfolderstatistics $MailboxIdentity #Getting complete list of selected mailbox
foreach ($mbxfolder in $fold){
#Define Folder View Really only want to return one object
$fvFolderView = new-object Microsoft.Exchange.WebServices.Data.FolderView(100) #page size for displayed folders
$fvFolderView.Traversal = [Microsoft.Exchange.WebServices.Data.FolderTraversal]::Deep; #Search traversal selection Deep = recursively
#Define a Search folder that is going to do a search based on the DisplayName of the folder
$SfSearchFilter = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo([Microsoft.Exchange.WebServices.Data.FolderSchema]::Displayname,$MBXFolder.name) #for each folder in mailbox define search
$findFolderResults = $MailboxRoot.FindFolders($SfSearchFilter,$fvFolderView) #for each folder in mailbox define folder view (this is online task for store.exe) and perform search
if ($findFolderResults.TotalCount -eq 0){ "Folder Doesn't Exist" } #Info if folder still exist
else {"Folder Exist"
ForEach ($Folder in $findFolderResults.Folders) { #for each folder in folder results perform check of folder class
$folder.folderclass #Info about folder class
if ($Folder.folderclass -eq "IPF.Imap"){ #If folder class is target type, do change and update
$Folder.folderclass = "IPF.Note" #Folder class change in variable
Write-Host "Updating folder $folder.name to correct type IPF.Note. Folder will start to be visible in OWA"
$Folder.update() #Folder class update in mailbox via EWS
}
}
}
}
}
It doesn't really make much sense to enumerate the the folders using Get-MailboxFolderStatistics and then search for each folder in EWS. This is going to really slow and unnecessary (you have the folderId anyway from Get-MailboxFolderStatistics so you can just convert that and bind to it). However I would
Get rid of Get-MailboxFolderStatistics altogether and just use straight EWS to enumerate the Folders in the Mailbox and do your fixes as this will be much quicker eg
## Get the Mailbox to Access from the 1st commandline argument
$MailboxName = $args[0]
## Load Managed API dll
###CHECK FOR EWS MANAGED API, IF PRESENT IMPORT THE HIGHEST VERSION EWS DLL, ELSE EXIT
$EWSDLL = (($(Get-ItemProperty -ErrorAction SilentlyContinue -Path Registry::$(Get-ChildItem -ErrorAction SilentlyContinue -Path 'Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Exchange\Web Services'|Sort-Object Name -Descending| Select-Object -First 1 -ExpandProperty Name)).'Install Directory') + "Microsoft.Exchange.WebServices.dll")
if (Test-Path $EWSDLL)
{
Import-Module $EWSDLL
}
else
{
"$(get-date -format yyyyMMddHHmmss):"
"This script requires the EWS Managed API 1.2 or later."
"Please download and install the current version of the EWS Managed API from"
"http://go.microsoft.com/fwlink/?LinkId=255472"
""
"Exiting Script."
exit
}
## Set Exchange Version
$ExchangeVersion = [Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2007_SP1
## Create Exchange Service Object
$service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService($ExchangeVersion)
## Set Credentials to use two options are availible Option1 to use explict credentials or Option 2 use the Default (logged On) credentials
#Credentials Option 1 using UPN for the windows Account
$psCred = Get-Credential
$creds = New-Object System.Net.NetworkCredential($psCred.UserName.ToString(),$psCred.GetNetworkCredential().password.ToString())
$service.Credentials = $creds
#$service.TraceEnabled = $true
#Credentials Option 2
#service.UseDefaultCredentials = $true
## Choose to ignore any SSL Warning issues caused by Self Signed Certificates
## Code From http://poshcode.org/624
## Create a compilation environment
$Provider=New-Object Microsoft.CSharp.CSharpCodeProvider
$Compiler=$Provider.CreateCompiler()
$Params=New-Object System.CodeDom.Compiler.CompilerParameters
$Params.GenerateExecutable=$False
$Params.GenerateInMemory=$True
$Params.IncludeDebugInformation=$False
$Params.ReferencedAssemblies.Add("System.DLL") | Out-Null
$TASource=#'
namespace Local.ToolkitExtensions.Net.CertificatePolicy{
public class TrustAll : System.Net.ICertificatePolicy {
public TrustAll() {
}
public bool CheckValidationResult(System.Net.ServicePoint sp,
System.Security.Cryptography.X509Certificates.X509Certificate cert,
System.Net.WebRequest req, int problem) {
return true;
}
}
}
'#
$TAResults=$Provider.CompileAssemblyFromSource($Params,$TASource)
$TAAssembly=$TAResults.CompiledAssembly
## We now create an instance of the TrustAll and attach it to the ServicePointManager
$TrustAll=$TAAssembly.CreateInstance("Local.ToolkitExtensions.Net.CertificatePolicy.TrustAll")
[System.Net.ServicePointManager]::CertificatePolicy=$TrustAll
## end code from http://poshcode.org/624
## Set the URL of the CAS (Client Access Server) to use two options are availbe to use Autodiscover to find the CAS URL or Hardcode the CAS to use
#CAS URL Option 1 Autodiscover
$service.AutodiscoverUrl($MailboxName,{$true})
"Using CAS Server : " + $Service.url
#CAS URL Option 2 Hardcoded
#$uri=[system.URI] "https://casservername/ews/exchange.asmx"
#$service.Url = $uri
## Optional section for Exchange Impersonation
#$service.ImpersonatedUserId = new-object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $MailboxName)
#Define Function to convert String to FolderPath
function ConvertToString($ipInputString){
$Val1Text = ""
for ($clInt=0;$clInt -lt $ipInputString.length;$clInt++){
$Val1Text = $Val1Text + [Convert]::ToString([Convert]::ToChar([Convert]::ToInt32($ipInputString.Substring($clInt,2),16)))
$clInt++
}
return $Val1Text
}
#Define Extended properties
$PR_FOLDER_TYPE = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(13825,[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Integer);
$folderidcnt = new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::MsgFolderRoot,$MailboxName)
#Define the FolderView used for Export should not be any larger then 1000 folders due to throttling
$fvFolderView = New-Object Microsoft.Exchange.WebServices.Data.FolderView(1000)
#Deep Transval will ensure all folders in the search path are returned
$fvFolderView.Traversal = [Microsoft.Exchange.WebServices.Data.FolderTraversal]::Deep;
$psPropertySet = new-object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties)
$PR_Folder_Path = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(26293, [Microsoft.Exchange.WebServices.Data.MapiPropertyType]::String);
#Add Properties to the Property Set
$psPropertySet.Add($PR_Folder_Path);
$fvFolderView.PropertySet = $psPropertySet;
#The Search filter will exclude any Search Folders
$sfSearchFilter = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo($PR_FOLDER_TYPE,"1")
$fiResult = $null
#The Do loop will handle any paging that is required if there are more the 1000 folders in a mailbox
do {
$fiResult = $Service.FindFolders($folderidcnt,$sfSearchFilter,$fvFolderView)
foreach($ffFolder in $fiResult.Folders){
$foldpathval = $null
#Try to get the FolderPath Value and then covert it to a usable String
if ($ffFolder.TryGetProperty($PR_Folder_Path,[ref] $foldpathval))
{
$binarry = [Text.Encoding]::UTF8.GetBytes($foldpathval)
$hexArr = $binarry | ForEach-Object { $_.ToString("X2") }
$hexString = $hexArr -join ''
$hexString = $hexString.Replace("FEFF", "5C00")
$fpath = ConvertToString($hexString)
}
"FolderPath : " + $fpath
"Folder Class : " + $ffFolder.FolderClass
}
$fvFolderView.Offset += $fiResult.Folders.Count
}while($fiResult.MoreAvailable -eq $true)
Cheers
Glen