Предложил своему руководителю вести учет простоев объектов вместо Excel таблицы в общем календаре Outlook. А раз назвался груздем, то и полезай в кузов, нужно как-то реализовать предложение. Решением задачи стало написание двух скриптов на powershell.

  • Первый скрипт создает новое событие в общем календаре Outlook с нужными данными. Тема события формируется из номера объекта и описания события разделенных ';'. Место события и идентификатор общего календаря (в котором будут создаваться новые события) берутся из конфигурационного файла SBS-ATMEvents.conf. В форме ввода нового события есть автозавершение номера объекта (нужно заполнить файл SBS-ATMList.txt необходимыми значениями).

  • Второй скрипт делает выборку событий за период. Результатом работы скрипта будет объект, с которым можно работать дальше (сортировать, фильтровать и т.п.). Скрипту Get-ATMEvents.ps1 можно указать в качестве параметров начало и конец периода выборки событий, а если запустить без параметров, выйдет форма выбора дат.

Текст скрипта создания события в общем календаре Outlook:

New-ATMEvent.ps1
```powershell
<#
Скрипт создания события в общем календаре Outlook

03.11.2016 Сатин Павел
#>

#Следующая фигня перезапускает скрипт в однопоточном режиме. Только для дого, что-бы форме работал AutoComplete
if ([System.Threading.Thread]::CurrentThread.ApartmentState -eq [System.Threading.ApartmentState]::MTA)
{
    powershell.exe -Sta -File $MyInvocation.MyCommand.Path
    return
}



#Определяем путь от куда запущен скрипт
$Global:CurrPath = $MyInvocation.MyCommand.Definition | split-path -parent
#Загружаем переменные из конфигурационного файла
Get-Content "$Global:CurrPath\SBS-ATMEvents.conf" | ForEach-Object { $ExecutionContext.InvokeCommand.InvokeScript($_) }

#Заполняем список УС
$Global:FileListUS = "$Global:CurrPath\$Global:FileListUS"
$Global:ListUS = Get-Content $Global:FileListUS


function NewOutlookEvent {

[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")

$objForm = New-Object System.Windows.Forms.Form
$objForm.Text = "Новое событие УС"
$objForm.Size = New-Object System.Drawing.Size(300,240)
$objForm.StartPosition = "CenterScreen"


$OKButton = New-Object System.Windows.Forms.Button
$OKButton.Location = New-Object System.Drawing.Size(75,160)
$OKButton.Size = New-Object System.Drawing.Size(75,23)
$OKButton.Text = "OK"
$OKButton.DialogResult = [System.Windows.Forms.DialogResult]::OK
$OKButton.Add_Click({$objForm.Close()})
$objForm.Controls.Add($OKButton)

$CancelButton = New-Object System.Windows.Forms.Button
$CancelButton.Location = New-Object System.Drawing.Size(150,160)
$CancelButton.Size = New-Object System.Drawing.Size(75,23)
$CancelButton.Text = "Cancel"
$CancelButton.DialogResult = [System.Windows.Forms.DialogResult]::Cancel
$CancelButton.Add_Click({$objForm.Close()})
$objForm.Controls.Add($CancelButton)

$objLabelStart = New-Object System.Windows.Forms.Label
$objLabelStart.Location = New-Object System.Drawing.Size(10,20)
$objLabelStart.Size = New-Object System.Drawing.Size(280,20)
$objLabelStart.Text = "Дата события:"
$objForm.Controls.Add($objLabelStart)


$datePicker = New-Object Windows.Forms.DateTimePicker
$datePicker.ShowUpDown = $false
#$datePicker.MinDate = $now
#$datePicker.MaxDate = $now.AddMonths(3); #arbitrary
#$datePicker.MaxSelectionCount = 1
$datePicker.Location = New-Object System.Drawing.Size(10,40)
$datePicker.Size = New-Object System.Drawing.Size(260,20)
#$datePicker.Width = 350
$datePicker.Format = "Custom"
$datePicker.CustomFormat = "dd.MM.yyyy HH:mm"
$datePicker.Enabled = $true
$objForm.Controls.Add($datePicker)


$objLabelUS = New-Object System.Windows.Forms.Label
$objLabelUS.Location = New-Object System.Drawing.Size(10,60)
$objLabelUS.Size = New-Object System.Drawing.Size(280,20)
$objLabelUS.Text = "Номер УС:"
$objForm.Controls.Add($objLabelUS)

$objComboBoxUS = New-Object System.Windows.Forms.ComboBox
$objComboBoxUS.Location = New-Object System.Drawing.Size(10,80)
$objComboBoxUS.Size = New-Object System.Drawing.Size(260,20)
#$objComboBoxUS.Height = 80

#Заполняем значения
$Global:ListUS | ForEach-Object {[void] $objComboBoxUS.Items.Add($_)}


$objComboBoxUS.Sorted = $true
$objComboBoxUS.AutoCompleteMode = [System.Windows.Forms.AutoCompleteMode]::SuggestAppend
$objComboBoxUS.AutoCompleteSource = [System.Windows.Forms.AutoCompleteSource]::ListItems
$objForm.Controls.Add($objComboBoxUS)

$objLabelDecr = New-Object System.Windows.Forms.Label
$objLabelDecr.Location = New-Object System.Drawing.Size(10,100)
$objLabelDecr.Size = New-Object System.Drawing.Size(280,20)
$objLabelDecr.Text = "Описание события:"
$objForm.Controls.Add($objLabelDecr)

$objEditBoxDescr = New-Object System.Windows.Forms.TextBox
$objEditBoxDescr.Location = New-Object System.Drawing.Size(10,120)
$objEditBoxDescr.Size = New-Object System.Drawing.Size(260,20)
$objForm.Controls.Add($objEditBoxDescr)


$objForm.Topmost = $True

$objForm.Add_Shown({$objForm.Activate()})
$resultForm = $objForm.ShowDialog()


if ($resultForm -eq [System.Windows.Forms.DialogResult]::OK) {


    $EventSubjectUS = $objComboBoxUS.Text
    $EventSubjectDescr = $objEditBoxDescr.Text

    Add-type -assembly "Microsoft.Office.Interop.Outlook" | out-null

    $outlook = new-object -comobject outlook.application
    $namespace = $outlook.GetNameSpace("MAPI")
    $folder = $namespace.GetFolderFromID($olFolders)

    $olAppointmentItem = 1

    $appt = $folder.Items.Add($olAppointmentItem)
    $appt.Start = $datePicker.Value
    #$appt.Duration = 60
    $appt.Subject = "$EventSubjectUS; $EventSubjectDescr"
    #$appt.Body = "Meet with Scripting Guys to discuss upcoming plans."
    $appt.Location = $aptLocation
    #$appt.ReminderMinutesBeforeStart = 15
    $appt.ReminderSet = $False

    $result = $appt.Save()

}

} #End NewOutlookEvent



NewOutlookEvent

```

Текст скрипта выборки событий из общего календаря Outlook:

Get-ATMEvents.ps1
```powershell
<#
Скрипт выборки событий из общего календаря Outlook

03.11.2016 Сатин Павел
#>

#Определяем путь от куда запущен скрипт
$Global:CurrPath = $MyInvocation.MyCommand.Definition | split-path -parent
#Загружаем переменные из конфигурационного файла
Get-Content "$Global:CurrPath\SBS-ATMEvents.conf" | ForEach-Object { $ExecutionContext.InvokeCommand.InvokeScript($_) }

#$Global:ReportStart =  [datetime]"11/01/2016"
#$Global:ReportEnd = [datetime]"11/07/2016"


Function FormDateQuery {

[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
[void] [System.Reflection.Assembly]::LoadWithPartialName("System.Drawing")

$objForm = New-Object System.Windows.Forms.Form
$objForm.Text = "Формирование отчета"
$objForm.Size = New-Object System.Drawing.Size(300,240)
$objForm.StartPosition = "CenterScreen"


$OKButton = New-Object System.Windows.Forms.Button
$OKButton.Location = New-Object System.Drawing.Size(75,160)
$OKButton.Size = New-Object System.Drawing.Size(75,23)
$OKButton.Text = "OK"
$OKButton.DialogResult = [System.Windows.Forms.DialogResult]::OK
$OKButton.Add_Click({$objForm.Close()})
$objForm.Controls.Add($OKButton)

$CancelButton = New-Object System.Windows.Forms.Button
$CancelButton.Location = New-Object System.Drawing.Size(150,160)
$CancelButton.Size = New-Object System.Drawing.Size(75,23)
$CancelButton.Text = "Cancel"
$CancelButton.DialogResult = [System.Windows.Forms.DialogResult]::Cancel
$CancelButton.Add_Click({$objForm.Close()})
$objForm.Controls.Add($CancelButton)

$objLabelStart = New-Object System.Windows.Forms.Label
$objLabelStart.Location = New-Object System.Drawing.Size(10,20)
$objLabelStart.Size = New-Object System.Drawing.Size(280,20)
$objLabelStart.Text = "Дата события:"
$objForm.Controls.Add($objLabelStart)


$datePicker = New-Object Windows.Forms.DateTimePicker
$datePicker.ShowUpDown = $false
#$datePicker.MinDate = $now
#$datePicker.MaxDate = $now.AddMonths(3); #arbitrary
#$datePicker.MaxSelectionCount = 1
$datePicker.Location = New-Object System.Drawing.Size(10,40)
$datePicker.Size = New-Object System.Drawing.Size(260,20)
#$datePicker.Width = 350
$datePicker.Format = "Custom"
$datePicker.CustomFormat = "dd.MM.yyyy HH:mm"
$datePicker.Enabled = $true
$objForm.Controls.Add($datePicker)

$objLabelEnd = New-Object System.Windows.Forms.Label
$objLabelEnd.Location = New-Object System.Drawing.Size(10,60)
$objLabelEnd.Size = New-Object System.Drawing.Size(280,20)
$objLabelEnd.Text = "Дата события:"
$objForm.Controls.Add($objLabelEnd)


$datePickerEnd = New-Object Windows.Forms.DateTimePicker
$datePickerEnd.ShowUpDown = $false
#$datePickerEnd.MinDate = $now
#$datePickerEnd.MaxDate = $now.AddMonths(3); #arbitrary
#$datePickerEnd.MaxSelectionCount = 1
$datePickerEnd.Location = New-Object System.Drawing.Size(10,80)
$datePickerEnd.Size = New-Object System.Drawing.Size(260,20)
#$datePickerEnd.Width = 350
$datePickerEnd.Format = "Custom"
$datePickerEnd.CustomFormat = "dd.MM.yyyy HH:mm"
$datePickerEnd.Enabled = $true
$objForm.Controls.Add($datePickerEnd)

$objForm.Topmost = $True


$objForm.Add_Shown({$objForm.Activate()})
$resultForm = $objForm.ShowDialog()

    if ($resultForm -eq [System.Windows.Forms.DialogResult]::OK) {
        $Global:ReportStart =  $datePicker.Value
        $Global:ReportEnd = $datePickerEnd.Value
    }

} #End FormDateQuery


Function QueryATMEvents {

Add-type -assembly "Microsoft.Office.Interop.Outlook" | out-null
#$olFolders = "Microsoft.Office.Interop.Outlook.OlDefaultFolders" -as [type]
$outlook = new-object -comobject outlook.application
$namespace = $outlook.GetNameSpace("MAPI")

$folder = $namespace.getFolderFromID($Global:olFolders)

$result = $folder.items | Select-Object -Property Start, End, Subject, Location |
    where-object { $_.start -gt $Global:ReportStart -AND $_.start -lt $Global:ReportEnd } | Sort-object Start

if ($result -ne $null) {

$result | Add-Member -MemberType NoteProperty -Name NumberUS -Value ""
$result | Add-Member -MemberType NoteProperty -Name Description -Value ""

foreach ($reselement in $result) {
    $DescStart = $reselement.Subject.IndexOf(";")
    $DescLen = ($reselement.Subject.Length - $DescStart)
    $NumUS = $reselement.Subject.Substring(0, $DescStart)
    $Descr = ($reselement.Subject.Substring($DescStart + 1, $DescLen - 1)).TrimStart(" ")
    #$reselement.Start = ($reselement.Start).Date
    $reselement.NumberUS = $NumUS
    $reselement.Description = $Descr
}

$result | Select-Object -Property Start, End, Location, NumberUS, Description

} else {

    Write-Host "За указанный период событий не найдено!"

}

} #End QueryATMEvents


########################### Main #######################################

if ($args[0] -ne $Null) {
    $Global:ReportStart =  [datetime]$args[0]

    if ($args[1] -ne $Null) {
        $Global:ReportEnd =  [datetime]$args[1]
    } else {
        $Global:ReportEnd =  Get-Date
    }

} else {
    FormDateQuery
}

$ATMEvents = QueryATMEvents
#Выводим без даты окончания события, так как пока в ней нет необходимости
$ATMEvents | Select-Object -Property Start, Location, NumberUS, Description

```

Файл с глобальными переменными (SBS-ATMEvents.conf):

$Global:aptLocation = "Реж"
$Global:olFolders = "000000000915DBD13ED6B14DAB56DB9764896838010092FB4C91DAB48E43ABA83FB5301ECF250000011E80310000"
$Global:FileListUS = "SBS-ATMList.txt"

Пример файла со значениями выпадающего списка objComboBoxUS (SBS-ATMList.txt):

144300
84150
15309
800090
190097

Идентификатор папки Outlook можно узнать с помощью следующего кода, который открывает форму Oulook с выбором папки.

Add-type -assembly "Microsoft.Office.Interop.Outlook" | out-null
$outlook = new-object -comobject outlook.application
$namespace = $outlook.GetNameSpace("MAPI")
$f = $namespace.PickFolder()

Write-Host $f.EntryID