Перейти к содержимому
www.mytechnote.ru
Меню
  • Администрирование
    • Скрипты
  • Оборудование
    • Обзоры
    • Инструкции
  • Прочее
  • Русский
    • English
    • Русский
Меню

Как удалить вложения из старых событий календаря в Exchange с помощью скрипта

Прилетела очередная, очень любопытная задача, являющаяся продолжением одной из прошлых. Нужно было сделать скрипт, который бы удалял вложения из старых прошедших встреч.

За основу взят метод, что и в прошлом скрипте — удаление будем производить с помощью EWS, соответственно у УЗ запускающей скрипт, должны быть права на имперсонацию.

Как пример, можно права можно добавить так:

New-ManagementRoleAssignment -Name "AllowImpersonation" -Role ApplicationImpersonation -User sanglyb@yourorg.com
New-RoleGroup -name ImpersonationGroup -Roles ApplicationImpersonation

Подробнее про права и роли, есть памятка.

Скрипт как обычно, загрузил на github, чуть что, скорее всего там будет последняя актуальная версия.

При запуске скрипта нужно указать обязательный параметр -mailbox — ящик в котором осуществляется поиск встреч и собраний, в формате user@yourorg.ru.

Также можно задать режим отчёта (-report $true), в этом случае, будет отображаться только статистика, без удаления вложений.

Также, можно задать в самом скрипте, либо также параметрами, глубину поиска в днях, и адрес вашего EWS.

Пример запуска:

./remove_attachements.ps -Mailbox user@yourorg.ru $false -DaysBack 90

Нужно учесть, то, как скрипт отрабатывает — он находит событие в календаре указанного пользователя, смотрит по событию, кто является организатором, если организатор не наш пользовать, то имперонируемся в EWS под организатором, удаляем вложение (если встреча в прошлом, либо если это завершенная повтораяющаяся встреча из прошлого), и рассылаем изменение всем участникам. Т.е. нужно иметь ввиду, что изменение собрания может иметь массовый характер, поэтому, использовать скрипт нужно максимально осторожно, на свой страх и риск!

Собственно, текст скрипта:

param(
    [Parameter(Mandatory=$true)]
    [string]$Mailbox,
    [switch]$Report=$false,
    [int]$DaysBack = 60,
#    [string]$Output = "past_meetings.csv",
    [string]$EwsUrl="https://exchangeserver/EWS/Exchange.asmx"
)

Write-Host "> Mailbox: $Mailbox" -ForegroundColor Cyan
Write-Host "> Report:  $Report"   -ForegroundColor Cyan
Write-Host "> EWS URL: $EwsUrl"   -ForegroundColor Cyan

#обход ошибок сертификата
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {
    param($sender, $certificate, $chain, $sslPolicyErrors)
    return $true
}


function New-EwsService {
    param(
        [string]$ImpersonateMailbox,
        [string]$Url
    )
    $svc = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService(
        [Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2019
    )

    $svc.UseDefaultCredentials = $true
    $svc.Url = $Url

    if ($ImpersonateMailbox) {
        $svc.ImpersonatedUserId = New-Object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId(
            [Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress,
            $ImpersonateMailbox
        )
    }
    return $svc
}

function Get-Appointments {
    param(
        [Parameter(Mandatory = $true)]
        [Microsoft.Exchange.WebServices.Data.ExchangeService]$Service,

        [Parameter(Mandatory = $true)]
        [DateTime]$Start,

        [Parameter(Mandatory = $true)]
        [DateTime]$End
    )

    # Разбиваем диапазон по 30 дней
    $step = 30
    $calendarFolders = Get-CalendarFolders -Service $Service
    $allAppointments = @()

    foreach ($folder in $calendarFolders) {

        $chunkStart = $Start

        while ($chunkStart -lt $End) {

            $chunkEnd = $chunkStart.AddDays($step)
            if ($chunkEnd -gt $End) { $chunkEnd = $End }

            # CalendarView на 30 дней
            $view = New-Object Microsoft.Exchange.WebServices.Data.CalendarView(
                $chunkStart, $chunkEnd, 1000
            )
            $view.PropertySet = [Microsoft.Exchange.WebServices.Data.PropertySet]::FirstClassProperties

            try {
                $page = $Service.FindAppointments($folder.Id, $view)
                if ($page.Items.Count -gt 0) {
                    $allAppointments += $page.Items
                }
            }
            catch {
                Write-Warning "Ошибка чтения папки $($folder.DisplayName): $_"
            }

            # Следующий интервал
            $chunkStart = $chunkEnd
        }
    }

    return $allAppointments
}

<#
function Get-Appointments {
    param(
        [Microsoft.Exchange.WebServices.Data.ExchangeService]$Service,
        [DateTime]$Start,
        [DateTime]$End
    )
	$calendarFolders = get-calendarFolders $service
	$view = New-Object Microsoft.Exchange.WebServices.Data.CalendarView($startDate, $endDate, 500)
	$view.PropertySet = [Microsoft.Exchange.WebServices.Data.PropertySet]::FirstClassProperties
	$appointments = @()
		foreach ($folder in $calendarFolders) {
		try {
			$items = $service.FindAppointments($folder.Id, $view)
			if ($items.Items.Count -gt 0) {
				$appointments += $items.Items
			}
		}
		catch {
			Write-Warning "Ошибка при чтении из папки $($folder.DisplayName): $_"
		}
	}
	return $appointments
}
#>

function Get-OriginalAppointment {
    param(
        [Microsoft.Exchange.WebServices.Data.ExchangeService]$Service,
        [string]$ICalUid,
        [DateTime]$Start,
        [DateTime]$End 
    )
	$appointments=Get-Appointments -service $service -start $start -end $end
	
    foreach ($appt in $appointments) {
        try { $appt.Load() } catch {}
        if ($appt.ICalUid -eq $ICalUid) {
            return $appt
        }
    }

    return $null
}

function get-calendarFolders {
    param(
        [Microsoft.Exchange.WebServices.Data.ExchangeService]$Service        
    )
	$root = [Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::MsgFolderRoot

	$folderView = New-Object Microsoft.Exchange.WebServices.Data.FolderView(10000)
	$folderView.Traversal = [Microsoft.Exchange.WebServices.Data.FolderTraversal]::Deep

	# Фильтр по типу папки
	$searchFilter = New-Object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo(
		[Microsoft.Exchange.WebServices.Data.FolderSchema]::FolderClass,
		"IPF.Appointment"
	)
	return $service.FindFolders($root, $searchFilter, $folderView)	
}

function Get-Master {
    param(
        [Microsoft.Exchange.WebServices.Data.Appointment]$OrgItem,
        [Microsoft.Exchange.WebServices.Data.ExchangeService]$OrgService
    )

    if ($OrgItem.AppointmentType -eq "Occurrence") {
        return [Microsoft.Exchange.WebServices.Data.Appointment]::BindToRecurringMaster(
            $OrgService,
            $OrgItem.Id
        )
    }

    return $OrgItem
}

function Save-OrgItemAndSendUpdate {
    param(
        [Microsoft.Exchange.WebServices.Data.Appointment]$OrgItem,
        [Microsoft.Exchange.WebServices.Data.ExchangeService]$OrgService
    )

    Write-Host "`nУдаление вложений из события $($OrgItem.Subject)..." -ForegroundColor green

    try {
        $OrgItem.Attachments.Clear()

        $OrgItem.Update(
            [Microsoft.Exchange.WebServices.Data.ConflictResolutionMode]::AlwaysOverwrite,
            [Microsoft.Exchange.WebServices.Data.SendInvitationsOrCancellationsMode]::SendToNone
        )

        $OrgItem.Load()
		#Изменение тела собрания, для коррекной рассылки уведомлениий об изменении
		
        #$OrgItem.Body = New-Object Microsoft.Exchange.WebServices.Data.MessageBody (
        #    $OrgItem.Body.Text + "<p>Attachement removed by script</p>"
        #)
		
		$body = $OrgItem.Body.Text
		$body = if ($body.EndsWith(" ")) {
			$body.TrimEnd()
		} else {
			$body + " "
		}
		$OrgItem.Body = New-Object Microsoft.Exchange.WebServices.Data.MessageBody $body


        $OrgItem.Update(
            [Microsoft.Exchange.WebServices.Data.ConflictResolutionMode]::AlwaysOverwrite,
            [Microsoft.Exchange.WebServices.Data.SendInvitationsOrCancellationsMode]::SendToAllAndSaveCopy
        )

        Write-Host "Обновление события выполнено" -ForegroundColor Green

    } catch {
        Write-Warning "Ошибка обнолвения события: $($_.Exception.Message)"
        if ($_.Exception.InnerException) {
            Write-Warning "Inner: $($_.Exception.InnerException.Message)"
        }
    }
}

$service  = New-EwsService -ImpersonateMailbox $Mailbox -Url $EwsUrl

$startDate = (Get-Date).AddDays(-$DaysBack)
$endDate   = (Get-Date).AddDays(1)
$appointments=Get-Appointments -service $service -start $startDate -end $endDate

$groups = $appointments | where-object {$_.HasAttachments -eq $true} | Group-Object ICalUid,AppointmentType
#$groups.group | ft *type*
#report

Write-Host "`nПодготовка данных..." -ForegroundColor Green
$allItems=@()
$results = foreach ($grp in $groups) {

    $item = $grp.Group | Select-Object -First 1
    $item.Load()
    if ($item.End -ge (Get-Date)) { continue }
    if (-not $item.HasAttachments) { continue }
	if ($item.AppointmentType -eq "RecurringMaster" -and $item.LastOccurrence.End -ge (get-date)) { continue }
    if ($item.AppointmentType -eq "Occurrence") {		
		if ($item.Attachments.Count -ge 1){
			$itemTMP = Get-Master $item $service
			if ($itemTMP.AppointmentType -eq "RecurringMaster" -and $itemTMP.LastOccurrence.End -ge (get-date)) { 
				$test="skipped"
				continue 
			} else {
				$test="not skipped"
				$item=$itemTMP
			}
		}
    }
	$allItems+=$item
    [pscustomobject]@{
        Mailbox          = $Mailbox
        Subject          = $item.Subject
        Start            = $item.Start
        End              = $item.End
        Organizer        = $item.Organizer.Address
        HasAttachments   = $item.HasAttachments
        AttachmentsCount = $item.Attachments.Count
		Attachments		 = $item.Attachments
        ItemType         = $item.AppointmentType
        ICalUid          = $item.ICalUid
		IsCancelled		 = $item.IsCancelled
		LastOccurrence	 = $item.LastOccurrence.End
		skipped			 = $skipped
    }
	
}

$allItems = $allItems | sort-object {$_.Organizer.Address}

if ($allItems.count -eq 0){
	Write-host "Не найдено событий календарей с вложениями." -ForegroundColor green
	exit
} else {
	write-host "Найдены старые события в календаре с вложениями" -ForegroundColor green
	$results 
	#$results | Export-Csv -NoTypeInformation -Encoding UTF8 -Path $Output
	#Write-Host "Отчёт сохранён в $Output" -ForegroundColor Green

}

# Если указан Report, только отчёт
if ($Report) {
    Write-Host "`nРежим Report. Удаление вложений не производится." -ForegroundColor Cyan
    exit
}


#Clean
$item=$null
	
	foreach ($item in $allItems){
		$organizer = $item.Organizer.Address
			# --- если организатор другой человек ---
		if ($organizer -and $organizer -ne $Mailbox) {
				try {
					if ($prevorganizer -ne $organizer) {
						Write-Host "Имперсонация $organizer" -ForegroundColor Yellow
						$orgService = New-EwsService -ImpersonateMailbox $organizer -Url $EwsUrl
						$prevorganizer=$Organizer
					}
					$origItem = Get-OriginalAppointment -Service $orgService -ICalUid $item.ICalUid -start $item.Start.AddHours(-2) -end $item.End.AddHours(2) #-AppointmentType $item.AppointmentType.tostring()

					if (-not $origItem) {
						Write-Warning "`n`n`nНе найден оригинал встречи у организатора. Удаляем вложение у пользователя $mailbox ..."
						Save-OrgItemAndSendUpdate $item $service
						continue
					}

					$origItem.Load()

				} catch {
					Write-Warning "Ошибка impersonation: $($_.Exception.Message)"
					continue
				}

				if ($origItem.AppointmentType -eq "Occurrence") {
					$itemTMP = $null
					$itemTMP = Get-Master $origItem $orgService
					if ($itemTMP.AppointmentType -eq "RecurringMaster" -and $itemTMP.LastOccurrence.end -ge (get-date)) { 
						Write-Warning "Дата завершения повторяющихся событий еще не наступила..."
						continue 
					} else {
						$origItem=$itemTMP
					}
				}
				Save-OrgItemAndSendUpdate $origItem $orgService
		}
		
		else {
			Save-OrgItemAndSendUpdate $item $service
		}
	}

Write-Host "`nЗавершено" -ForegroundColor Green
Опубликовано в 11.12.202511.12.2025 от sanglyb

Навигация по записям

← Как скачать и установить Office 2024 LTSC
Проблема при использовании библиотеки PassFiltEx на старых процессорах →

Добавить комментарий Отменить ответ

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Подписывайтесь

Youtube
vk
telegram
github
twitter
rss
  • Форум
  • Об авторе
  • Поддержать автора

Популярные записи

Новые записи

  • Как расширить LVM раздел в Linux
  • Исправление ошибки SrsResources.dll после обновления SSRS для SCCM
  • Проблема при использовании библиотеки PassFiltEx на старых процессорах
  • Как удалить вложения из старых событий календаря в Exchange с помощью скрипта
  • Как скачать и установить Office 2024 LTSC

Архивы

  • 2026 (3)
  • 2025 (5)
  • 2024 (7)
  • 2023 (6)
  • 2022 (5)
  • 2021 (25)
  • 2020 (34)
  • 2019 (39)
  • 2018 (78)
  • 2017 (56)
  • 2016 (77)
  • 2015 (9)
© 2015-2026 www.mytechnote.ru
Использование материалов сайта возможно только с разрешения владельца или обязательна индексируемая, прямая, передающая вес ссылка на материал.