0
点赞
收藏
分享

微信扫一扫

Exchange邮箱在Outlook中的文件夹被自动复制

故障背景

用户Outlook邮箱中的某文件夹一直被自动复制。

故障示例

用户user@contoso.com邮箱中Drafts文件夹一直被自动复制,即使删除了,仍然会自动再生成。需要调查什么原因以及什么账号执行的操作。

解决过程

  1. 使用如下命令导出用户邮箱的DatabaseEvent Log,命名为databaseevent.csv:

$db = (get-mailbox username).database
$mb = (get-mailbox username).exchangeguid
Get-DatabaseEvent $db -MailboxGuid $mb -ResultSize unlimited | Export-Csv D:\databaseevent.csv

Exchange邮箱在Outlook中的文件夹被自动复制_Powershell

  1. 使用如下脚本Get-MailboxFolderPermissionEWS.ps1导出用户邮箱中Folder信息,命名为ewsdata.txt,并查找到文件名Drafts1对应的HexEntryID,脚本内容如下:

[CmdletBinding()]
Param (
    [parameter(ValueFromPipelineByPropertyName=$true,Mandatory=$true, Position=0)]
    [Alias('PrimarySmtpAddress')]
    $EmailAddress,

    [parameter( Mandatory=$false, Position=1)]
    [System.Management.Automation.PsCredential]$Credentials,

    [parameter( Mandatory=$false, Position=2)]
    [System.Management.Automation.SwitchParameter]$Impersonate,

    [parameter( Mandatory=$false, Position=3)]
    [System.Management.Automation.SwitchParameter]$CalendarOnly,

    [parameter( Mandatory=$false, Position=4)]
    [ValidateSet("MsgFolderRoot", "Root")]
    [System.String]$RootFolder="MsgFolderRoot",

    [parameter( Mandatory=$false, Position=5)]
    [System.String]$Server,

    [parameter( Mandatory=$false, Position=6)]
    [ValidateRange(0,20)]
    [System.Int16]$Threads= '15',

    [parameter( Mandatory=$false, Position=7)]
    [System.Management.Automation.SwitchParameter]$MultiThread,

    [parameter( Mandatory=$false, Position=8)]
    [System.Int16]$MaxResultTime='240',

    [parameter( Mandatory=$false, Position=9)]
    [ValidateScript({If (!($_.Contains('.\')) -and (Test-Path $_ -PathType leaf)) {$True} Else {Write-Warning "MrMapi could not be found or relative path used!Please use absolute path!"}})]
    [System.String]$MrMapi,

    [parameter( Mandatory=$false, Position=10)]
    [System.Management.Automation.SwitchParameter]$UseMrMapi,

    [parameter( Mandatory=$false, Position=11)]
    [System.Management.Automation.SwitchParameter]$TrustAnySSL,

    [parameter( Mandatory=$false, Position=12)]
    [System.Management.Automation.SwitchParameter]$SearchUnknownOnly,

    [parameter( Mandatory=$false, Position=13)]
    [System.Management.Automation.SwitchParameter]$ClearUnknown

)

Begin {
    #sanity check for MrMapi
    If ($UseMrMapi -and !($MrMapi)) {
        Write-Warning "Please provide path for MrMapi!"
        Break
    }
    #initiate runspace and make sure we are using single-threaded apartment STA
    $Jobs = @()
    $Sessionstate = [system.management.automation.runspaces.initialsessionstate]::CreateDefault()
    $RunspacePool = [RunspaceFactory]::CreateRunspacePool(1, $Threads,$Sessionstate, $Host)
    $RunspacePool.ApartmentState = "STA"
    $RunspacePool.Open()
    [System.Int32]$j='1'
    $Timer = [System.Diagnostics.Stopwatch]::StartNew()
}

Process {

    #start function Get-MailboxFolderPermission
    function Get-MailboxFolderPermission {
        Param(
            [parameter( ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$true,Mandatory=$true, Position=0)]
            [Alias('PrimarySmtpAddress')]
            [System.String]$EmailAddress,

            [parameter( Mandatory=$false, Position=1)]
            [System.Management.Automation.PsCredential]$Credentials,

            [parameter( Mandatory=$false, Position=2)]
            [System.Boolean]$Impersonate=$false,
    
            [parameter( Mandatory=$false, Position=3)]
            [System.Boolean]$CalendarOnly=$false,

            [parameter( Mandatory=$false, Position=4)]
            [ValidateSet("MsgFolderRoot", "Root")]
            [System.String]$RootFolder="MsgFolderRoot",

            [parameter( Mandatory=$false, Position=5)]
            [System.String]$Server,

            [parameter( Mandatory=$false, Position=6)]
            [ValidateScript({If (!($_.Contains('.\')) -and (Test-Path $_ -PathType leaf)) {$True} Else {Write-Warning "MrMapi could not be found or relative path used!Please use absolute path!"}})]
            [System.String]$MrMapi,

            [parameter( Mandatory=$false, Position=7)]
            [System.Boolean]$UseMrMapi,

            [parameter( Mandatory=$false, Position=8)]
            [System.Boolean]$TrustAnySSL,

            [parameter( Mandatory=$false, Position=9)]
            [System.Int32]$ProgressID,

            [parameter( Mandatory=$false, Position=10)]
            [System.Boolean]$SearchUnknownOnly,

            [parameter( Mandatory=$false, Position=11)]
            [System.Boolean]$ClearUnknown
        )

        function Cleanup-FolderPermission
        {
            [CmdletBinding()]
            Param(
                [parameter(ValueFromPipelineByPropertyName=$true,Mandatory=$false, Position=0)]
                [Alias('PrimarySmtpAddress')]
                $EmailAddress,
                
                [parameter(Mandatory=$true, Position=1)]
                [System.String]$FolderID,
                
                [parameter(Mandatory=$true, Position=2)]
                [System.String]$ChangeKey,
        
                [parameter( Mandatory=$false, Position=3)]
                [System.Management.Automation.PsCredential]$Credentials,
        
                [parameter( Mandatory=$false, Position=4)]
                [System.Boolean]$Impersonate,
                
                [parameter( Mandatory=$true, Position=5)]
                $URL

            )

            Begin
            {
                [System.Boolean]$returnValue = $true
                # SOAP templates
                $GetFolder = "
                    <?xml version=`"1.0`" encoding=`"utf-8`"?>
                      <soap:Envelope xmlns:xsi=`"http://www.w3.org/2001/XMLSchema-instance`" xmlns:m=`"http://schemas.microsoft.com/exchange/services/2006/messages`" xmlns:t=`"http://schemas.microsoft.com/exchange/services/2006/types`" xmlns:soap=`"http://schemas.xmlsoap.org/soap/envelope/`">
                        <soap:Header>
                        <t:RequestServerVersion Version=`"Exchange2010_SP2`" />"
                If ($Impersonate)
                {
                    $GetFolder += "
                        <t:ExchangeImpersonation>
                            <t:ConnectingSID>
                            <t:SmtpAddress>$($EmailAddress)</t:SmtpAddress>
                            </t:ConnectingSID>
                        </t:ExchangeImpersonation>"
                }

                $GetFolder += "
                    </soap:Header>
                      <soap:Body>
                      <m:GetFolder>
                          <m:FolderShape>
                          <t:BaseShape>IdOnly</t:BaseShape>
                          <t:AdditionalProperties>
                              <t:FieldURI FieldURI=`"folder:PermissionSet`" />
                          </t:AdditionalProperties>
                          </m:FolderShape>
                          <m:FolderIds>
                          <t:FolderId Id=`"$($FolderID)`" ChangeKey=`"$($ChangeKey)`" />
                          </m:FolderIds>
                      </m:GetFolder>
                      </soap:Body>
                    </soap:Envelope>"

                $Update = "
                    <?xml version=`"1.0`" encoding=`"utf-8`"?>
                      <soap:Envelope xmlns:soap=`"http://schemas.xmlsoap.org/soap/envelope/`" xmlns:t=`"http://schemas.microsoft.com/exchange/services/2006/types`">
                        <soap:Header>
                        <t:RequestServerVersion Version=`"Exchange2010_SP2`" />"

                If ($Impersonate)
                {
                    $Update += "
                        <t:ExchangeImpersonation>
                            <t:ConnectingSID>
                            <t:SmtpAddress>$($EmailAddress)</t:SmtpAddress>
                            </t:ConnectingSID>
                        </t:ExchangeImpersonation>"
                }

                $Update += "
                    </soap:Header>
                      <soap:Body>
                        <UpdateFolder xmlns=`"http://schemas.microsoft.com/exchange/services/2006/messages`" xmlns:t=`"http://schemas.microsoft.com/exchange/services/2006/types`">
                          <FolderChanges>
                            <t:FolderChange>
                              <t:FolderId Id=`"$($FolderID)`" ChangeKey=`"$ChangeKey`"/>
                              <t:Updates>
                                <t:SetFolderField>
                                <t:FieldURI FieldURI=`"folder:PermissionSet`"/>
                                  <t:FolderType>
                                    <t:Bla>
                                    </t:Bla>
                                  </t:FolderType>
                                </t:SetFolderField>
                              </t:Updates>
                            </t:FolderChange>
                          </FolderChanges>
                        </UpdateFolder>
                      </soap:Body>
                    </soap:Envelope>"

            }
            Process
            {
                try{
                    $GetParams = @{
                        Uri = $Url
                        Method = 'Post'
                        Body = $($GetFolder.TrimStart())
                        ContentType = 'text/xml'
                        Verbose = $false
                    }

                    $Headers = @{}

                    if ($Credentials)
                    {
                        $CredString = "$($Credentials.UserName.ToString()):$($Credentials.GetNetworkCredential().password.ToString())"
                        $encodedCreds = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($CredString))
                        $Headers.Add('Authorization',"Basic $($encodedCreds)")
                    }
                    else
                    {
                        $GetParams.Add('UseDefaultCredentials',$true)
                    }

                    #query folder
                    $Headers.Add('Accept','application/xml')
                    $Headers.Add('Accept-Encoding','gzip,deflate')
                    $GetParams.Add('Headers',$Headers)
                    [XML]$GetResp = (Invoke-WebRequest @GetParams).Content

                    if (-not [System.String]::IsNullOrEmpty($GetResp))
                    {
                        #query response for PermissionSet
                        $root = $GetResp.get_DocumentElement()
                        $NamespaceURI = "http://schemas.microsoft.com/exchange/services/2006/types"
                        $ns = New-Object System.Xml.XmlNamespaceManager($GetResp.NameTable)
                        $ns.AddNameSpace("ns",$NamespaceURI)
                        $XPath  = "//PermissionSet"
                        $XPath = $XPath -replace "/(?!/)", "/ns:"
                        $PermNode = $root.SelectSingleNode($XPath,$ns)

                        if ($PermNode)
                        {
                            #replace FolderType with the current type
                            $type = $GetResp.Envelope.Body.GetFolderResponse.ResponseMessages.GetFolderResponseMessage.Folders.ChildNodes.LocalName
                            $Update = $Update.Replace('FolderType',$type)
                            #convert to XML
                            [xml]$Update = $Update.TrimStart()
                            #import PermissionSet XML node
                            $Update.Envelope.Body.UpdateFolder.FolderChanges.FolderChange.Updates.SetFolderField.$type.AppendChild($Update.ImportNode($root.SelectSingleNode($XPath,$ns),$true)) | Out-Null
                            #remove helper XML node
                            $updateRoot = $Update.get_DocumentElement()
                            $NamespaceURI = "http://schemas.microsoft.com/exchange/services/2006/types"
                            $ns = New-Object System.Xml.XmlNamespaceManager($update.NameTable)
                            $ns.AddNameSpace("ns",$NamespaceURI)
                            $BlaNode = $updateRoot.SelectSingleNode("//ns:Bla",$ns)
                            $UnknownEntries = $updateRoot.SelectSingleNode("//ns:UnknownEntries",$ns)
                            $BlaNode.get_ParentNode().RemoveChild($BlaNode) | Out-Null
                            $UnknownEntries.get_ParentNode().RemoveChild($UnknownEntries) | Out-Null

                            $UpdateParams = @{
                                Uri = $Url
                                Method = 'Post'
                                Body = $Update
                                ContentType = 'text/xml'
                                Verbose = $false
                            }

                            $UpdateParams.Add('Headers',$Headers)
                            if (-not $Credentials)
                            {
                                $UpdateParams.Add('UseDefaultCredentials',$true)
                            }

                            $UpdatePost = Invoke-WebRequest @UpdateParams
                        }
                        
                    }
                }
                catch{
                    Write-Verbose "Error occured while clearing UnknownEntries"
                    $returnValue = $false
                }
            }
            End
            {
                $returnValue
            }
        }

        try {
            $MailboxName = $EmailAddress
            ## 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]::Exchange2010_SP2

            ## Create Exchange Service Object
            $service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService($ExchangeVersion)
            #$service.PreAuthenticate = $true
            ## Set Credentials to use two options are availible Option1 to use explict credentials or Option 2 use the Default (logged On) credentials
            If ($Credentials) {
                #Credentials Option 1 using UPN for the windows Account
                #$psCred = Get-Credential
                $psCred = $Credentials
                $creds = New-Object System.Net.NetworkCredential($psCred.UserName.ToString(),$psCred.GetNetworkCredential().password.ToString())
                $service.Credentials = $creds
                #$service.TraceEnabled = $true
            }
            Else {
                #Credentials Option 2
                $service.UseDefaultCredentials = $true
            }

            If ($TrustAnySSL) {
                ## 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
            If ($Server) {
                #CAS URL Option 2 Hardcoded
                $uri=[system.URI] "https://$server/ews/exchange.asmx"
                $service.Url = $uri
            }
            Else {
                #CAS URL Option 1 Autodiscover
                $service.AutodiscoverUrl($MailboxName,{$true})
                #"Using CAS Server : " + $Service.url
            }

            ## Optional section for Exchange Impersonation
            If ($Impersonate) {
                $service.ImpersonatedUserId = new-object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $MailboxName)
                If ($Service.HttpHeaders.keys.Contains("X-AnchorMailbox")) {
                    $Service.HttpHeaders.Remove("X-AnchorMailbox") | Out-Null
                }
                $Service.HttpHeaders.Add("X-AnchorMailbox", $MailboxName)
            }

            $rootFolderId = new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::$RootFolder,$MailboxName)
            #Define Extended properties
            $PR_FOLDER_TYPE = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(13825,[Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Integer);
            $folderidcnt = $rootFolderId
            #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;

            If ($CalendarOnly) {
                # Search only for Calendar
                $sfSearchFilter = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo([Microsoft.Exchange.WebServices.Data.FolderSchema]::FolderClass, "IPF.Appointment")
            }
            Else {
                #The Search filter will exclude any Search Folders
                $sfSearchFilter = new-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo($PR_FOLDER_TYPE,"1")
            }

            $fiResult = $null
            #loop through folders, when more than 1000
            do {
                $fiResult = $Service.FindFolders($folderidcnt,$sfSearchFilter,$fvFolderView)
                # Add Properties for the Folder Property Set
                $PR_NT_SECURITY_DESCRIPTOR = new-object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(0x0E27, [Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Binary);
                $folderPropset = new-object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties)
                If ($UseMrMapi){
                    $folderPropset.Add($PR_NT_SECURITY_DESCRIPTOR)
                }
                Else {
                    $folderPropset.Add([Microsoft.Exchange.WebServices.Data.FolderSchema]::Permissions)
                }
                $folderPropset.Add($PR_Folder_Path)

                # Anonymous
                $STANDARDUSER_ANONYMOUS = [Microsoft.Exchange.WebServices.Data.StandardUser]::Anonymous
                # Default
                $STANDARDUSER_DEFAULT = [Microsoft.Exchange.WebServices.Data.StandardUser]::Default

                $objcol = @()

                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
                }

                #helper function. Thanks to Danijel Klaric
                function BinToHex {
                    param(
                        [Parameter(
                            Position=0,
                            Mandatory=$true,
                            ValueFromPipeline=$true)
                    ]
                    [Byte[]]$Bin)
                    # assume pipeline input if we don't have an array (surely there must be a better way)
                    If ($bin.Length -eq 1) {
                        $bin = @($input)
                    }
                    $return = -join ($Bin | foreach {"{0:X2}" -f $_ })
                    Write-Output $return
                }

                function Get-SID {
                    param (
                        [parameter( Mandatory=$False, Position=0)]
                        [System.String]$Domain=$env:USERDOMAIN,
                        [parameter( Mandatory=$true, Position=1)]
                        [System.String]$User
                    )
                    try {
                        $objUser = New-Object System.Security.Principal.NTAccount("$Domain","$User")
                        [System.Security.Principal.SecurityIdentifier]$sid = $objUser.Translate([System.Security.Principal.SecurityIdentifier])
                        $sid
                    }
                    catch {
                        #create object
                        $returnValue = New-Object -TypeName PSObject
                        #get all properties from last error
                        $ErrorProperties =$Error[0] | Get-Member -MemberType Property
                        #add existing properties to object
                        foreach ($Property in $ErrorProperties){
                            if ($Property.Name -eq 'InvocationInfo'){
                                $returnValue | Add-Member -Type NoteProperty -Name 'InvocationInfo' -Value $($Error[0].InvocationInfo.PositionMessage)
                            }
                            else {
                                $returnValue | Add-Member -Type NoteProperty -Name $($Property.Name) -Value $($Error[0].$($Property.Name))
                            }
                        }
                        #return object
                        $returnValue
                    }
                }

                function Get-UserForSID {
                    param (
                        [parameter( Mandatory=$true, Position=0)]
                        [System.String]$SID
                    )
                    try {
                        $objSID = New-Object System.Security.Principal.SecurityIdentifier("$SID")
                        $objUser = $objSID.Translate( [System.Security.Principal.NTAccount])
                        $objUser.Value
                    }
                    catch {
                        #create object
                        $returnValue = New-Object -TypeName PSObject
                        #get all properties from last error
                        $ErrorProperties =$Error[0] | Get-Member -MemberType Property
                        #add existing properties to object
                        foreach ($Property in $ErrorProperties){
                            if ($Property.Name -eq 'InvocationInfo'){
                                $returnValue | Add-Member -Type NoteProperty -Name 'InvocationInfo' -Value $($Error[0].InvocationInfo.PositionMessage)
                            }
                            else {
                                $returnValue | Add-Member -Type NoteProperty -Name $($Property.Name) -Value $($Error[0].$($Property.Name))
                            }
                        }
                        #return object
                        $returnValue
                    }
                }

                [System.Int32]$i='1'
                ForEach ($Folder in $fiResult) {
                    Write-Progress `
                            -id $ProgressID `
                            -ParentId 1 `
                            -Activity "Processing mailbox - $($MailboxName) with $($fiResult.Folders.count) folders" `
                            -PercentComplete ( $i / $fiResult.Folders.count * 100) `
                            -Status "Remaining: $($fiResult.Folders.count - $i) processing folder: $($Folder.DisplayName)"
                    If (($Folder.Displayname -ne 'System') -and ($Folder.Displayname -ne 'Audits')) {
                        #Write-Output "Working on $($Folder.Displayname)"
                        #Load Properties
                        $Folder.Load($folderPropset)
                        #$Folder.ExtendedProperties[0].Value

                        $foldpathval = $null
                        #Try to get the FolderPath Value and then covert it to a usable String
                        If ($Folder.TryGetProperty($PR_Folder_Path,[ref] $foldpathval)) {
                            $binarry = [Text.Encoding]::UTF8.GetBytes($foldpathval)
                            $hexArr = $binarry | ForEach-Object { $_.ToString("X2") }
                            $hexString = $hexArr -join ''
                            #$hexString
                            #$hexString = $hexString.Replace("FEFF", "5C00")
                            $hexString = $hexString.Replace("EFBFBE", "5C")
                            $fpath = ConvertToString($hexString)
                        }
                        #set alternateID
                        $alternateID = new-object Microsoft.Exchange.WebServices.Data.AlternateId("EwsId",$Folder.ID,$MailboxName)
                        If ($UseMrMapi){
                            #get PR_NT_SECURITY_DESCRIPTOR
                            #save HEX Value to file and parse using MRMAPI
                            $tmpfile = [System.IO.Path]::GetTempFileName()
                            BinToHex -Bin ($Folder.ExtendedProperties[0].Value) | Out-File -FilePath $tmpfile -Encoding ascii
                            #& $MrMapi -Parsertype 19 -Input $tmpfile

                            #option 1: run MrMapi.exe directly
                            #$RawFolderSD = & $MrMapi -Parsertype 19 -Input $tmpfile
                            #$FolderSD = $RawFolderSD | %{$_ | ?{($_ -match 'Account*') -or ($_ -match 'SID:*')}} | %{$_ -replace ' ','' -replace 'Account:','' -replace 'SID:'}
                            #$FolderSD = $FolderSD -join ';' -replace ';S-1',"=S-1" -split ';' | select -Unique

                            #option 2: using [System.Diagnostics.Process] and wait for the process
                            $ps = new-object System.Diagnostics.Process
                            $ps.StartInfo.Filename = $MrMapi
                            $ps.StartInfo.Arguments = "-Parsertype 19 -Input $tmpfile"
                            $ps.StartInfo.RedirectStandardOutput = $True
                            $ps.StartInfo.UseShellExecute = $false
                            $ps.start()| Out-Null
                            [string]$RawFolderSD = $ps.StandardOutput.ReadToEnd()
                            $ps.WaitForExit()
                            $FolderSD = $RawFolderSD.Split("`r")| ?{($_ -match 'Account*') -or ($_ -match 'SID:*')} | %{$_ -replace ' ','' -replace 'Account:','' -replace 'SID:' -replace "`n",''}
                            $FolderSD = $FolderSD -join ';' -replace ';S-1',"=S-1" -split ';' | select -Unique
                            # remove temp file
                            Remove-Item $tmpfile
                            If ($FolderSD) {
                                ForEach ($entry in $FolderSD) {
                                    $User = $Entry.Split('=')[0].Split('\')[1]
                                    $LocalSID = (Get-SID -User $User).Value
                                    $data = new-object PSObject
                                    $data | add-member -type NoteProperty -Name Mailbox -Value $MailboxName
                                    $data | add-member -type NoteProperty -Name User -Value $($Entry.Split('=')[0])
                                    $data | add-member -type NoteProperty -Name SIDinSD -Value $($Entry.Split('=')[1])
                                    $data | add-member -type NoteProperty -Name LocalSID -Value $LocalSID
                                    $data | add-member -type NoteProperty -Name FolderName -Value $Folder.DisplayName
                                    $data | add-member -type NoteProperty -Name FolderType -Value $Folder.FolderClass
                                    $data | add-member -type NoteProperty -Name FolderPath -Value $fpath
                                    $data | add-member -type NoteProperty -Name EwsID -Value $Folder.ID
                                    $data | add-member -type NoteProperty -Name StoreID -Value ($service.ConvertId($alternateID,'StoreID')).UniqueId
                                    $data | add-member -type NoteProperty -Name HexEntryID -Value ($service.ConvertId($alternateID,'HexEntryId')).UniqueId
                                    $objcol += $data
                                }
                            }
                        }
                        ElseIf ($SearchUnknownOnly -or $ClearUnknown){
                            ForEach ($Unknown in $Folder.Permissions.UnknownEntries) {
                                $data = new-object PSObject
                                $data | add-member -type NoteProperty -Name Mailbox -Value $MailboxName
                                $data | add-member -type NoteProperty -Name User -Value $Unknown
                                $data | add-member -type NoteProperty -Name FolderName -Value $Folder.DisplayName
                                $data | add-member -type NoteProperty -Name FolderType -Value $Folder.FolderClass
                                $data | add-member -type NoteProperty -Name FolderPath -Value $fpath
                                $data | add-member -type NoteProperty -Name EwsID -Value $Folder.ID.UniqueId
                                $data | add-member -type NoteProperty -Name EwsChangeKey -Value $Folder.ID.ChangeKey
                                $data | add-member -type NoteProperty -Name StoreID -Value ($service.ConvertId($alternateID,'StoreID')).UniqueId
                                $data | add-member -type NoteProperty -Name HexEntryID -Value ($service.ConvertId($alternateID,'HexEntryId')).UniqueId
                                $objcol += $data
                            }

                            If ($ClearUnknown -and (($Folder.Permissions.UnknownEntries).Count -gt 0)) {
                                $FolderPermissionParams = @{
                                    EmailAddress = $MailboxName
                                    FolderID = $Folder.ID.UniqueId
                                    ChangeKey = $Folder.ID.ChangeKey
                                    Impersonate = $Impersonate
                                    URL = $service.Url
                                    Verbose = $VerbosePreference
                                }
                                If ($Credentials) {
                                    $FolderPermissionParams.Add('Credential',$Credentials)
                                }
                                #$ResultValue = Cleanup-FolderPermission -EmailAddress $MailboxName -FolderID $Folder.ID.UniqueId -ChangeKey $Folder.ID.ChangeKey -Credentials $creds -Impersonate $Impersonate -URL $service.Url
                                $ResultValue = Cleanup-FolderPermission @FolderPermissionParams
                                If ($ResultValue) {
                                    Write-Verbose "Succesfully cleaned folder $($Folder.DisplayName)"
                                }
                                Else {
                                    Write-Verbose "Couldn't clean folder $($Folder.DisplayName)with UnknownEntry $($Folder.Permissions.UnknownEntries[0])"
                                }
                            }
                        }
                        Else {
                            ForEach ($Perm in $Folder.Permissions) {
                                $data = new-object PSObject
                                $data | add-member -type NoteProperty -Name Mailbox -Value $MailboxName
                                If ($Perm.UserId.StandardUser -eq $STANDARDUSER_ANONYMOUS){
                                    $data | add-member -type NoteProperty -Name User -Value "Anonymous"
                                }
                                ElseIf($Perm.UserId.StandardUser -eq $STANDARDUSER_DEFAULT){
                                    $data | add-member -type NoteProperty -Name User -Value "Default"
                                }
                                Else {
                                    $data | add-member -type NoteProperty -Name User -Value $Perm.UserId.PrimarySmtpAddress
                                }
                                $data | add-member -type NoteProperty -Name Permissions -Value $Perm.DisplayPermissionLevel
                                $data | add-member -type NoteProperty -Name SID -Value $Perm.UserId.SID
                                $data | add-member -type NoteProperty -Name FolderName -Value $Folder.DisplayName
                                $data | add-member -type NoteProperty -Name FolderType -Value $Folder.FolderClass
                                $data | add-member -type NoteProperty -Name CanCreateItems -Value $Perm.CanCreateItems
                                $data | add-member -type NoteProperty -Name CanCreateSubFolders -Value $Perm.CanCreateSubFolders
                                $data | add-member -type NoteProperty -Name IsFolderOwner -Value $Perm.IsFolderOwner
                                $data | add-member -type NoteProperty -Name IsFolderVisible -Value $Perm.IsFolderVisible
                                $data | add-member -type NoteProperty -Name IsFolderContact -Value $Perm.IsFolderContact
                                $data | add-member -type NoteProperty -Name EditItems -Value $Perm.EditItems
                                $data | add-member -type NoteProperty -Name DeleteItems -Value $Perm.DeleteItems
                                $data | add-member -type NoteProperty -Name ReadItems -Value $Perm.ReadItems
                                $data | add-member -type NoteProperty -Name FolderPath -Value $fpath
                                $data | add-member -type NoteProperty -Name EwsID -Value $Folder.ID
                                $data | add-member -type NoteProperty -Name StoreID -Value ($service.ConvertId($alternateID,'StoreID')).UniqueId
                                $data | add-member -type NoteProperty -Name HexEntryID -Value ($service.ConvertId($alternateID,'HexEntryId')).UniqueId
                                $objcol += $data
                            }
                            }
                        $i++ | Out-Null
                    }
                #end loop of folders
                }
                $objcol
                $fvFolderView.Offset += $fiResult.Folders.Count
            }while($fiResult.MoreAvailable -eq $true)

        }
        catch{
            #create object
            $returnValue = New-Object -TypeName PSObject
            #get all properties from last error
            $ErrorProperties =$Error[0] | Get-Member -MemberType Property
            #add existing properties to object
            foreach ($Property in $ErrorProperties){
                if ($Property.Name -eq 'InvocationInfo'){
                    $returnValue | Add-Member -Type NoteProperty -Name 'InvocationInfo' -Value $($Error[0].InvocationInfo.PositionMessage)
                }
                else {
                    $returnValue | Add-Member -Type NoteProperty -Name $($Property.Name) -Value $($Error[0].$($Property.Name))
                }
            }
            #return object
            $returnValue
        }
        Write-Progress -id $ProgressID -ParentId 1 -Activity "Processing mailbox - $($MailboxName) with $($fiResult.Folders.count) folders" -Status "Ready" -Completed
        #end function Get-MailboxFolderPermission
    }

    #if multi-threaded create jobs
    If ($MultiThread) {
        #create scriptblock from function
        $ScriptBlock = [scriptblock]::Create((Get-ChildItem Function:\Get-MailboxFolderPermission).Definition)
        ForEach($Address in $EmailAddress) {
            try{
                $j++ | Out-Null
                #Write-Host "Adding job for "$Address
                $MailboxName = $Address
                #$PowershellThread = [powershell]::Create().AddScript($ScriptBlock).AddParameter('EmailAddress',$MailboxName).AddParameter('Credentials',$Credentials).AddParameter('Impersonate',$Impersonate).AddParameter('UseDefaultCred',$UseDefaultCred).AddParameter('CalendarOnly',$CalendarOnly).AddParameter('RootFolder',$RootFolder).AddParameter('Server',$Server)
                $PowershellThread = [powershell]::Create().AddScript($ScriptBlock).AddParameter('EmailAddress',$MailboxName)
                $PowershellThread.AddParameter('Credentials',$Credentials) | Out-Null
                $PowershellThread.AddParameter('Impersonate',$Impersonate) | Out-Null
                $PowershellThread.AddParameter('CalendarOnly',$CalendarOnly) | Out-Null
                $PowershellThread.AddParameter('RootFolder',$RootFolder) | Out-Null
                $PowershellThread.AddParameter('Server',$Server) | Out-Null
                If ($MrMapi) {
                    $PowershellThread.AddParameter('MrMapi',$MrMapi) | Out-Null
                }
                If ($SearchUnknownOnly) {
                    $PowershellThread.AddParameter('SearchUnknownOnly',$SearchUnknownOnly) | Out-Null
                }
                If ($ClearUnknown) {
                    $PowershellThread.AddParameter('ClearUnknown',$ClearUnknown) | Out-Null
                }
                $PowershellThread.AddParameter('UseMrMapi',$UseMrMapi) | Out-Null
                $PowershellThread.AddParameter('TrustAnySSL',$TrustAnySSL) | Out-Null
                $PowershellThread.AddParameter('ProgressID',$j) | Out-Null
                $PowershellThread.RunspacePool = $RunspacePool
                $Handle = $PowershellThread.BeginInvoke()
                $Job = "" | Select-Object Handle, Thread, object
                $Job.Handle = $Handle
                $Job.Thread = $PowershellThread
                $Job.Object = $Address
                $Jobs += $Job
            }
            catch {
                #create object
                $returnValue = New-Object -TypeName PSObject
                #get all properties from last error
                $ErrorProperties =$Error[0] | Get-Member -MemberType Property
                #add existing properties to object
                foreach ($Property in $ErrorProperties){
                    if ($Property.Name -eq 'InvocationInfo'){
                        $returnValue | Add-Member -Type NoteProperty -Name 'InvocationInfo' -Value $($Error[0].InvocationInfo.PositionMessage)
                    }
                    else {
                        $returnValue | Add-Member -Type NoteProperty -Name $($Property.Name) -Value $($Error[0].$($Property.Name))
                    }
                }
                #return object
                $returnValue
            }
        }
    }
    #if not mutli-threaded start sequential processing
    Else{
        Get-MailboxFolderPermission @psBoundParameters
    }
}

End{

    #monitor and retrieve the created jobs
    If ($MultiThread) {
        $SleepTimer = 200
        $ResultTimer = Get-Date
        While (@($Jobs | Where-Object {$_.Handle -ne $Null}).count -gt 0) {
            $Remaining = "$($($Jobs | Where-Object {$_.Handle.IsCompleted -eq $False}).object)"
            If ($Remaining.Length -gt 60){
                $Remaining = $Remaining.Substring(0,60) + "..."
            }
            Write-Progress `
                -id 1 `
                -Activity "Waiting for Jobs - $($Threads - $($RunspacePool.GetAvailableRunspaces())) of $Threads threads running" `
                -PercentComplete (($Jobs.count - $($($Jobs | Where-Object {$_.Handle.IsCompleted -eq $False}).count)) / $Jobs.Count * 100) `
                -Status "$(@($($Jobs | Where-Object {$_.Handle.IsCompleted -eq $False})).count) remaining - $Remaining"

            ForEach ($Job in $($Jobs | Where-Object {$_.Handle.IsCompleted -eq $True})) {
                $Job.Thread.EndInvoke($Job.Handle)
                $Job.Thread.Dispose()
                $Job.Thread = $Null
                $Job.Handle = $Null
                $ResultTimer = Get-Date
            }

            If (($(Get-Date) - $ResultTimer).totalseconds -gt $MaxResultTime) {
                Write-Warning "Child script appears to be frozen for $($Job.Object), try increasing MaxResultTime"
                #Exit
            }

            Start-Sleep -Milliseconds $SleepTimer
            # kill all incomplete threads when hit "CTRL+q"
            If ($Host.UI.RawUI.KeyAvailable) {
                $KeyInput = $Host.UI.RawUI.ReadKey("IncludeKeyUp,NoEcho")
                If (($KeyInput.ControlKeyState -cmatch '(Right|Left)CtrlPressed') -and ($KeyInput.VirtualKeyCode -eq '81')) {
                    Write-Host -fore red "Kill all incomplete threads....."
                        ForEach ($Job in $($Jobs | Where-Object {$_.Handle.IsCompleted -eq $False})) {
                            Write-Host -fore yellow "Stopping job $($Job.Object) ...."
                            $Job.Thread.Stop()
                            $Job.Thread.Dispose()
                        }
                    Write-Host -fore red "Exit script now!"
                    Exit
                }
            }
        }
        # clean-up
        $RunspacePool.Close() | Out-Null
        $RunspacePool.Dispose() | Out-Null
        [System.GC]::Collect()
    }
    Write-Verbose "Total runtime:$($Timer.Elapsed.ToString())"
}

Exchange邮箱在Outlook中的文件夹被自动复制_复制_02

Exchange邮箱在Outlook中的文件夹被自动复制_权限_03

  1. 在步骤1导出的数据中根据ItemEntryId列 (选择值与步骤2查找到的HexEntryID值相同的条目)以及EventName列(选择值为ObjectCreated)进行筛选,可以得出:
  • ClientCategory=MOMT(代表Outlook客户端)
  • PrincipalName=Contoso\user01(执行操作的账号)

Exchange邮箱在Outlook中的文件夹被自动复制_复制_04

  1. 使用命令>Get-MailboxFolderPermission -Identity user@contoso.com,可以得出用户user01对邮箱 user
    @contoso.com 的权限为{PublishingEditor}:

Exchange邮箱在Outlook中的文件夹被自动复制_Outlook_05

  1. 在Outlook中查,Publishing Editor有Create subfolders的权限:

Exchange邮箱在Outlook中的文件夹被自动复制_Exchange_06

解决方案

将用户user01对user@contoso.com邮箱的Permission Level更改为不具有Create subfolders的其它级别即可。


举报

相关推荐

0 条评论