Заметки

Оглавление

22.03.2017
Автомобиль - Сброс сервисного интервала в Skoda Roomster

Алгоритм сброса счетчика сервисного интервала в Skoda Roomster 2012 года выпуска.

  1. При выключенном зажигании зажать кнопку сброса суточного пробега.
  2. Не отпуская кнопку, включить зажигание (двигатель запускать не надо) и через несколько секунд отпустить кнопку.
  3. Кнопкой папок переключить сервисный интервал в режим "---" (или выставить напоминание в "15000")
  4. Выключить зажигание.

Метод подходит и для других моделей Skoda с аналогичной приборной панелью (Fabia, Octavia, Yeti).

 

10.03.2017
Быт - Модель железной дороги (часть 4)

Начало в заметке "Быт - Модель железной дороги (часть 3)".

Изготовление подмакетника

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

Для того, чтобы воплотить в жизнь рельсовый план, спроектированный в предыдущей заметке, нужно разработать основу, на которой будет крепиться рельсовый путь, путевые сооружения и автоматизация пути, а так же ландшафт и постройки.

Рельсовый план макета

Обычно для подмакетника используется влагостойкая фанера толщиной от 8 мм, хотя некоторые моделисты используют ДВП, МДФ, ПВХ. Для своего подмакетника я буду использовать фанерный лист OSB толщиной 9 мм.

Достоинства применения влагостойкой фанеры OSB перед другими материалами:

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

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

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

Размещение приводов стрелок

Приняв во внимание ландшафт макета и расположение приводов стрелок, получаем следующий каркас жесткости подмакетника.

Каркас жесткости подмакетника

Для того, чтобы проложить провода под макетом, потребуются технологические отверстия в рейках каркаса. Так как провода должны идти не только от системы управления до стрелок, но и до датчиков занятости пути и до светофоров, то необходимо иметь в наличии схему расположения светофоров и датчиков.

Схема разделения рельсового пути на блок-участки

Когда схема блок-участков известна, то можно начертить схему прокладки проводов по макету, то есть в подмакетнике, чтобы в процессе изготовления основы сразу просверлить отверстия для проводов (хотя это можно сделать и позднее).

Схема электропроводки макета

 

 

 

Продолжение в заметке "Быт - Модель железной дороги (часть 5)".

 

06.03.2017
Быт - Модель железной дороги (часть 3)

Начало в заметке "Быт - Модель железной дороги (часть 2)".

Рельсовые планы макетов

После того, как определена концепция макета и его возможные размеры, можно переходить к проектированию.

Схему пути можно взять готовую, изучив форумы моделистов, а можно нарисовать самому. Существует несколько программ для разработки макетов, например WinTrack, WinRail, AnyRail, SCARM или RTS. В программах присутствуют библиотеки элементов путей и строений, доступных на рынке, поэтому использование таких программ значительно упрощает разработку макетов. Я рекомендую использовать программу WinTrack версии 10 (эта старая версия не требует активации и ее можно легко найти на торрентах).

В предыдущей заметке я обозначил следующие входные данные для макета:

  • Сложность: любительский макет для игры с детьми дома
  • Масштаб: HO (1:87).
  • Доступное пространство: 100 см х 250 см.
  • Количество уровней: 2 уровня (в начале задумывался только 1, но потом захотелось 2).
  • Количество станций: 2 пассажирских (вокзал и платформа) и 1 грузовая.
  • Количество путей: 1 колея.
  • Количество поездов: 2 поезда.
  • Производитель путевого материала: Piko
  • Управление: комбинированное (в начале задумывалось аналоговое, но потом захотелось управлять поездами независимо).

Первый вариант макета у меня поучился таким, как показано на рисунках ниже.



В этом варианте я не планировал использовать автоматизацию стрелок, а поездами я хотел управлять по аналоговому методу, используя изоляторы на участках путей. Например пассажирский поезд идет по главному пути, грузовой поезд стоит на станции, его участок пути обесточен. Когда пассажирский поезд заходит на привокзальный путь, то этот участок обесточивается, а на пути грузовой станции подается напряжении - теперь можно управлять грузовым поездом.

Проанализировав элементы, которые используются в схеме пути я пришел к выводу, что схема неоптимальна, так как в ней используются несколько элементов, которые обычно не идут в стартовых наборах Piko (прямые рельсы 55205 и радиусные рельсы 55213).

Во втором варианте я решил использовать как можно больше элементов путей, которые можно встретить в стартовых наборах. Ниже на рисунках представлен план рельсового пути и вид макета второй версии.



Когда я показал его на форуме моделистов (ссылка на сообщения форума тут), мне подсказали, что грузовая станция расположена неудобно - чтобы заехать в нее, поезду сначала надо заходить в тупик, а потом уже осаживать состав на станцию. Поэтому я переделал макет в третий вариант, показанный на рисунках ниже.



Получившаяся схема пути так же имела две пассажирских станции и одну грузовую. Въезд на грузовую станцию был непосредственно с магистрали, но мне не понравилось, что в левой верхней части макета много неиспользованного пространства, поэтому я решил развернуть грузовую станцию на 180 градусов и дополнить ее дополнительным тупиком.



Получился достаточно интересный макет с красивым ландшафтом и интересной путевой схемой для игры с детьми. Управление планировалось аналоговое - на некоторых стыках рельс видны засечки изоляторов для частичного обесточивания участков пути. Но тут мне захотелось второй уровень для макета и цифровое управление поездами - так получился пятый вариант.



В пятом варианте макета было два уровня: один для игры, а второй для разворотной петли. Первый уровень состоял из кольцевой однопутной магистрали, вокзала, остановочной платформы, грузовой станции, двух тупиков (депо и завод) и спуска на нижний уровень. Нижний уровень состоял из теневого вокзала и разворотной петли.

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

Тем не менее этот вариант макета имел два существенных недостатка:
- уклон спуска/подъема между уровнями в 8% (рекомендуемый уклон - 4 %),
- путь перехода между уровнями проходил под стрелочным механизмом, что требовало либо замены привода стрелки на макетную, либо еще большего увеличения угла спуска/подъема, чтобы прийти под стрелку и подмакетный механизм перевода её перевода с расстоянием между уровнями в 15 см.

Финальный вариант, который я разработал исключал все недостатки. Макет получился со следующим содержанием:

  • Однопутная кольцевая магистраль.
  • Пассажирский вокзал с двумя платформами, один из путей станции - тупик.
  • Остановочная пассажирская платформа на магистрали.
  • Депо на один локомотив между пассажирской платформой и грузовой станцией.
  • Грузовая станция на два тупика с въездом непосредственно с магистрали.
  • Дополнительный тупик для пакгауза с въездом от грузовой станции.
  • Ответвление от магистрали к тупику для установки подвижного состава на рельсы.
  • Ответвление от магистрали к спуску на нижний уровень.
  • Нижний уровень с разворотной петлей и без станций.
  • Автоматические стрелки с приводами, расположенными под макетом.
  • Цифровое управление поездами с помощью командной станции Piko Digi 1.
  • Аналоговое управление стрелками с помощью кнопочного пульта макета.

Наклон спуска/подъема в этом варианте получился в 7% (я протестировал прохождение такого уклона локомотивом Piko BR 218 - он справляется с ним запросто даже с загруженным вагоном - видео теста тут). Так же все пути второго уровня проходят под свободнам пространством первого уровня - подмакетные приводы стрелок не мешают проходу поезда.

Ниже на рисунках представлены схемы путей обоих уровней, схема высот, наполнение макета объектами и трехмерное моделирование макета в сборе.








Список элементов путевой схемы макета:

Артикул Описание Набор
PIKO 57170
Набор
PIKO 57155
Набор
PIKO 55330
Итого в
наборах
Требуется Не хватает
55200 Рельсы прямые G239 1 3 9 13 13 OK
55201 Рельсы прямые G231 7 9 7 23 23 OK
55212 Рельсы радиусные R2 12 14 0 26 32 +6
55219 Рельсы радиусные R9 0 0 2 2 2 OK
55220  Стрелка левая WL 0 0 3 3 3 OK
55221 Стрелка правая WR 1 0 2 3 3 OK
55222 Стрелка левая радиусная BWL 0 1 0 1 1 OK
55223 Стрелка правая радиусная BWR 0 1 0 1 1 OK
55209 Рельс-флекс G940 0 0 0 0 1 +1
55280 Тупиковый упор 1 0 5 6 6 OK

То есть для рельсового пути макета понадобится 3 стартовых набора (грузовой поезд с рельсами AB, пассажирский поезд с рельсами AE, грузовая станция с рельсами D), а так же 6 радиусных рельс типа R2 и один гибкий рельс G940.

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

Продолжение в заметке "Быт - Модель железной дороги (часть 4)".

 

05.03.2017
Быт - Модель железной дороги (часть 2)

Начало в заметке "Быт - Модель железной дороги (часть 1)".

С чего начать

Рекламный плакат из журнала Железная дорога в миниатюре
Рекламный плакат из журнала "Железная дорога в миниатюре"

Первое, с чего следует начать железнодорожное моделирование - это определиться с доступным бюджетом. Чтобы построить самый простой макет железной дороги потребуется минимум 50 000 рублей в ценах 2016 года.

Далее необходимо понять, макет какой эпохи хочется построить. Существует несколько эпох, которые не рекомендуется смешивать при покупке подвижного состава и строений, чтобы не нарушать эстетику проекта:

  • I эпоха - с 1835 по 1920 год - первые паровозы;
  • II эпоха - с 1920 по 1945 год - паровозы;
  • III эпоха - с 1945 по 1970 год - паровозы и первые тепловозы;
  • IV эпоха - с 1970 по 1985 год - тепловозы и первые электровозы;
  • V эпоха - с 1985 года по 2005 год - тепловозы и электровозы;
  • VI эпоха - с 2006 года по настоящее время - скоростные тепловозы и электровозы.

Так же необходимо определиться со страной, которую будет представлять макет. На российском рынке хорошо представлены подвижной состав и постройки для Германии, Австрии, США и России (перечислены в порядке уменьшения доступности).

Следующим пунктом в выборе можно поставить сложность макета, который хочется построить. Здесь можно перечислить следующие основные параметры любительского макета (под любительским я подразумеваю домашний макет для игры с ребенком и для показа гостям):

  • Масштаб макета может быть G - 1:22,5, 00 - 1:76,2, H0 - 1:87, TT - 1:120, N - 1:160, Z - 1:220. Я выбрал масштаб 1:87, поэтому далее я буду рассматривать только HO.
  • Размер макета или доступное пространство в доме (минимальное пространство должно быть 100 см х 130 см). Мой макет будет размером 100 см х 250 см.
  • Количество уровней в макете. Минимальное расстояние между уровнями должно быть 15 см: 9 см на локомотив, 1 см на путь и насыпь, 5 см на толщину подмакетника (стол-основу для макета) и подмакетные механизмы. Я буду делать 2 уровня: верхний - игровой, нижний - служебный (для разворотной петли).
  • Схема макета: количество станций, количество путей, схемы разъездов, количество составов. В моем макете будет две пассажирских станции (большой вокзал с тупиком и остановочная платформа на основном пути) и одна грузовая станция с 3 тупиками. Главный путь будет одноколейным, а двигаться будут 2 состава: пассажирский и грузовой.
  • Ландшафт макета (горы, холмы, равнины, овраги, ущелья). Мой макет будет иметь только горы, в которых будут прорублены тоннели.
  • Производитель путевого материала и подвижного состава может быть один, а может сочетаться из разных, так как большинство элементов стандартизировано. В России хорошо представлены следующие производители: Piko, Roco, Bachmann, Mehano. Для себя я выбрал Piko.
  • Система управления макетом может быть аналоговой и цифровой, а может быть комбинированной (цифровой для подвижного состава и аналоговой для периферии). Я выбрал комбинированный тип.
Примечание. Более подробную информацию о макетах можно подчерпнуть в книге "Модели железных дорог" - Барковсков Б.В. (1989), а так же на форумах моделистов, например тут.
Пример макета железной дороги
Пример макета железной дороги

Когда все основные моменты выяснены, можно переходить к составлению плана путей и чертежу подмакетника.

Продолжение в заметке "Быт - Модель железной дороги (часть 3)".

 

04.03.2017
Быт - Модель железной дороги (часть 1)

Вступление

В начале декабря 2016 года на Youtube в списке рекомендованных к просмотру роликов мне было предложено видео с моделью железной дороги. В детстве мне всегда хотелось иметь домашнюю железную дорогу, но во времена СССР это было дефицитом и стоило дорого, так как импортировалось из ГДР, поэтому детская мечта не осуществилась. Сейчас, когда мне на глаза попалось видео с моделью железной дороги, я загорелся идеей купить себе такую. Первым делом я сделал поиск в Яндексе и изучил ассортимент интернет магазинов - цены на интересные аналоговые наборы начинались от 12 000 рублей. Поэтому я решил заглянуть на Авито, где я сразу же нашел предложение на интересный стартовый набор Piko 57170 за 8500 рублей (продавец был из Нижнего Новгорода). Посоветовавшись с женой мы решили купить этот набор в качестве подарка мне на Новый год.


Стартовый набор Piko 57170 - Грузовой поезд австрийских железных дорог

Погоняв поезд по кругу, я понял, что мне мало только одного пути и одного поезда - я хочу железнодорожную сеть, несколько поездов и инфраструктуру вдоль путей.

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

Продолжение в заметке "Быт - Модель железной дороги (часть 2)".

 

20.02.2017
Servers - Удаление старых файлов (версия 2)

В процессе работы сервера или компьютера на дисках накапливаются различные файлы, которые занимают полезное пространство, а иногда даже могут привести к остановке работы операционной системы из-за нехватки свободного места. По этой причине время от времени необходимо очищать папки с временными и журнальными файлами. Чтобы избежать ручной работы, я рекомендую использовать скрипты, которые можно поставить в планировщик Windows и проводить автоматическую чистку.

В статье "Примеры скриптов для администрирования" я уже публиковал VBS-скрипт, который вычищает папки на серверах Windows Server 2008. В этой заметке я приведу пример Powershell-скрипта для очистки папок на сервере Windows Server 2012 (хотя на этой версии операционной системы старый VBS-скрипт работает так же, как и на старой).

# Powershell 3.0 or later is required
# Does not work at SYSTEM account in Windows 2008 R2

# --- Declare variables ---
$FoldersToPurge = New-Object System.Collections.ArrayList

# --- Input data ---

[void] $FoldersToPurge.Add( 
  @{
    "Path" = 'C:\Windows\Temp';
    "Subfolders" = $True;
    "Mask" = '*';
    "Exclude" = $Nothing;
    "DaysOld" = 60
  }
)

[void] $FoldersToPurge.Add(
  @{
    "Path" = 'D:\RDSProfiles\RDS_SLQTools';
    "Subfolders" = $False;
    "Mask" = '*';
    "Exclude" = @("UVHD-template.vhdx");
    "DaysOld" = 90
  }
)

# --- Functions ---

Function Write-ScriptLog {
  Param(
    [CmdletBinding()] 
    [Parameter(ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
    [String]$Message,
    [Parameter(ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
    [String]$LogFile
  )
  Process {
    $LogMessage = Get-Date -uformat "%d.%m.%Y %H:%M:%S"
    $LogMessage += "`t"
    $LogMessage += $Message
    $LogMessage | Out-File -FilePath $LogFile -Append
  }
}#End Function

Function Get-FolderSize {
  Param(
    [CmdletBinding()] 
    [Parameter(ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
    [String]$Path
  )
  Begin {
    $oFSO = New-Object -comobject Scripting.FileSystemObject
  }
  Process{
    $oFolder = $oFSO.GetFolder($Path)
    Return ($oFolder.Size)
  } 
 } # End Function

Function Purge-Folder {
  Param(
    [CmdletBinding()] 
    [Parameter(ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
    [String]$Path,
    [Parameter(ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
    [Boolean]$Subfolders,
    [Parameter(ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
    [String]$Mask,
    [Parameter(ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
    [String[]]$Exclude,
    [Parameter(ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
    [int]$DaysOld
  )
  Process {
    If ($DaysOld -lt 1) {
      Write-ScriptLog -LogFile $LogFile -Message ("Error: " + $DaysOld + " days is set for folder " + $Path)
      Return
    }
    If (Test-Path $Path -PathType Container) {
      Write-ScriptLog -LogFile $LogFile -Message ("Purging " + $Path + `
        " folder by deleing content which is older then " + $DaysOld + " days")
      # Получение коллекции файлов
      $Files= Get-ChildItem ($Path+"\*") -Force -File -Include $Mask
      # Обработка каждого файла из коллекции
      ForEach ($CurrentFile in $Files) {
        $CurrentFileWriteDate = $CurrentFile.LastWriteTime
        $CurrentFileCreateDate = $CurrentFile.CreationTime
        $CurrentFileOld_c = ((Get-Date) - $CurrentFileCreateDate).Days
        $CurrentFileOld_w = ((Get-Date) - $CurrentFileWriteDate).Days
        # Проверка, является ли файл устаревшим
        If (($CurrentFileOld_c -gt $DaysOld) -and ($CurrentFileOld_w -gt $DaysOld)) {
          $SkipFlag = $False
          ForEach ($ExcludeFile in $Exclude) {
            If ($CurrentFile.Name -eq $ExcludeFile) {
              $SkipFlag = $True
            }
          }
          If (-Not($SkipFlag)) {
            Write-ScriptLog -LogFile $LogFile -Message ("--> Delete " + $CurrentFile.FullName + `
              " (Created " + $CurrentFileCreateDate.ToString("dd.MM.yyyy") + ")")
            # Удаление устаревшего файла
            Try {
              Remove-Item $CurrentFile.FullName -Force -ErrorAction Stop
            } Catch {
              Write-ScriptLog -LogFile $LogFile -Message ("----> Error: " + $_.Exception.Message)
            }
          } Else {
            Write-ScriptLog -LogFile $LogFile -Message ("--> Skip " + $CurrentFile.FullName + `
              " (Created " + $CurrentFileCreateDate.ToString("dd.MM.yyyy") + ")")
          }

          Start-Sleep -Milliseconds 20
        }
      }
      If ($Subfolders) {
        # Получение коллекции подпапок
        $Folders= Get-ChildItem $Path -Force -Directory
        # Обработка каждой подпапки
        ForEach ($CurrentFolder in $Folders) {
          # Рекурсивный вызов процедуры удаления старых файлов - подпрограмма вызывает сама себя
          Purge-Folder -Path $CurrentFolder.FullName -Subfolders $True -Mask $Mask -Exclude $Exclude `
            -DaysOld $DaysOld
          # Проверка размера папки
          $CurrentFolderSize = Get-FolderSize -Path $CurrentFolder.FullName
          $CurrentFolderCreationDate = $CurrentFolder.CreationTime
          $CurrentFolderOld_c = ((Get-Date) - $CurrentFolderCreationDate).Days
          If (($CurrentFolderSize -eq 0) -and ($CurrentFolderOld_c -gt $DaysOld)) {
            Write-ScriptLog -LogFile $LogFile -Message ("--> Delete " + $CurrentFolder.FullName + `
              " (Created " + $CurrentFolderCreationDate.ToString("dd.MM.yyyy") + ")")
            # Удаление пустой папки
            Try {
              Remove-Item $CurrentFolder.FullName -Force -Confirm:$False -ErrorAction Stop
            } Catch {
              Write-ScriptLog -LogFile $LogFile -Message ("----> Error: " + $_.Exception.Message)
            }
          }
        }
      }
    } Else {
      Write-ScriptLog -LogFile $LogFile -Message ("Error: " + $Path + " folder does not exist")
      Return
    }
  }
} # End Function

# --- Start ---

$ScriptName =  $MyInvocation.MyCommand.Name
$ScriptFolder = $MyInvocation.MyCommand.Path.SubString(0,($MyInvocation.MyCommand.Path.Length - `
  $MyInvocation.MyCommand.Name.Length))
$LogFile = $ScriptFolder + 'Logs\' + (Get-Date -format yyyy_MM_dd) + "_" + `
  $MyInvocation.MyCommand.Name.SubString(0,($MyInvocation.MyCommand.Name.Length - 4)) + ".log"
$WindowsFolder = $env:windir
$WinTempFolder = $WindowsFolder + "\Temp"

# Log-file creation
If (-not(Test-Path ($ScriptFolder + 'Logs') -PathType Container )) {
  New-Item -ItemType Directory -Path ($ScriptFolder + 'Logs')
}
Out-File -FilePath $LogFile

Write-ScriptLog -LogFile $LogFile -Message ("Start " + $ScriptName)
Write-ScriptLog -LogFile $LogFile -Message ("===================================")

# Purge folders
ForEach ($CurrentFolderToPurge in $FoldersToPurge) {
  Write-ScriptLog -LogFile $LogFile -Message ("===== Processing " + `
    $CurrentFolderToPurge.Path + " folder =====")
  Purge-Folder -Path $CurrentFolderToPurge.Path -Subfolders $CurrentFolderToPurge.Subfolders `
    -Mask $CurrentFolderToPurge.Mask -Exclude $CurrentFolderToPurge.Exclude `
    -DaysOld $CurrentFolderToPurge.DaysOld
}

Write-ScriptLog -LogFile $LogFile -Message ("===================================")
Write-ScriptLog -LogFile $LogFile -Message ("Stop " + $ScriptName)

Использование сценария: в разделе --- Input data --- необходимо ввести секцию добавления элемента к массиву FoldersToPurge, где указать путь к очищаемой папке Path, флаг обработки подпапок Subfolders, маску обработки файлов Mask, список исключений Exclude (одномерный массив из имен файлов), период устаревания DaysOld в днях.

Если флаг обработки подпапок Subfolders установлен в $True, то удаление происходит по рекурсивному методу - сценарий просматривает дату создания каждого файла в папке и удаляет старые файлы, а так же обрабатывает все подпапки текущей папки по тому же принципу.

Указанный скрипт рекомендуется поставить в планировщик Windows на ежедневное выполнение в ночные часы, когда на серверах нет пользователей. Запуск задания нужно выполнять от имени системы (от имени пользователя SYSTEM).

 

17.02.2017
Servers - Скрипт обслуживания сервера RDS (версия 2)

В заметках "Скрипт обслуживания сервера RDS" и "PowerShell - Удаление профилей с сервера" я уже рассказывал о скрипте для очистки профилей и очереди печати на терминальных серверах. В этой публикации я размещаю обновленный текст кода Powershell, который я оптимизировал для обслуживания терминальных серверов Windows Server 2012.

# --- Description ---
# This script is for maintainance of Windows 2012 RDS server in roaming profile mode.
# - remove users from access to RDP
# - force users logoff from the server
# - delete broken profile folders (folders must be removed after user logoff)
# - delete broken profile registry section (registry sections must be removed after user logoff)
# - delete profile oprimization scheduled tasks
# - stop print spooler, delete all print jobs, run DeleteInactivePortSilently.exe, start print spooler
# - restore users access to RDP
# - reboot the server on Monday

# --- Declare variables ---

$ExcludedProfilePaths = New-Object System.Collections.ArrayList
$ExcludedRegistrySections = New-Object System.Collections.ArrayList

# --- Input data ---

# !!!!!   DON'T FORGET TO EXCLUDE SERVICE ACCOUNTS. LOCAL ACCOUNTS WILL BE EXCLUDED AUTOMATICALLY   !!!!!

$ProfileFolder = "C:\Users"
$ProfileRegistrySection = "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList"
$DomainRDSGroup = "RDS_Application_Users"
$LocalRDSGroup = "Remote Desktop Users"
$ZipArhiverPath = "C:\Scripts\7za.exe"
[void] $ExcludedProfilePaths.AddRange( ("C:\Users\All Users", "C:\Users\Default", "C:\Users\Default User", `
  "C:\Users\Public") )
[void] $ExcludedRegistrySections.AddRange( ("S-1-5-18", "S-1-5-19", "S-1-5-20") )

# --- Functions ---

Function Write-ScriptLog {
  Param(
    [CmdletBinding()] 
    [Parameter(ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
    [String]$Message,
    [Parameter(ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
    [String]$LogFile
  )
  Process {
    $LogMessage = Get-Date -uformat "%d.%m.%Y %H:%M:%S"
    $LogMessage += "`t"
    $LogMessage += $Message
    $LogMessage | Out-File -FilePath $LogFile -Append
  }
}#End Function

Function Get-ComputerSessions {
  Param(
    [CmdletBinding()] 
    [Parameter(ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
    [String]$Computer
  )
  Process {
    $Report = quser /server:$Computer | Select-Object -Skip 1 | ForEach-Object {
      $CurrentLine = $_.Trim() -Replace '\s+',' ' -Split '\s'
      $HashProps = @{
        UserName = $CurrentLine[0]
        ComputerName = $Computer
      }
      # If session is disconnected, different fields will be selected
      If ($CurrentLine[2] -eq 'Disc') {
        $HashProps.SessionName = $null
        $HashProps.Id = $CurrentLine[1]
        $HashProps.State = $CurrentLine[2]
        $HashProps.IdleTime = $CurrentLine[3]
        $HashProps.LogonTime = $CurrentLine[4..6] -join ' '
      } Else {
        $HashProps.SessionName = $CurrentLine[1]
        $HashProps.Id = $CurrentLine[2]
        $HashProps.State = $CurrentLine[3]
        $HashProps.IdleTime = $CurrentLine[4]
        $HashProps.LogonTime = $CurrentLine[5..7] -join ' '
      }
      New-Object -TypeName PSCustomObject -Property $HashProps |
        Select-Object -Property UserName,ComputerName,SessionName,Id,State,IdleTime,LogonTime
    }
    Return $Report 
  }
}#End Function

Function Process-DeleteProfileFolders {
  Param(
    [CmdletBinding()] 
    [Parameter(ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true,Mandatory=$true)]
    [String]$ProfileFolder,
    [Parameter(ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true,Mandatory=$true)]
    [String[]]$ExcludedProfiles,
    [Parameter(ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true,Mandatory=$false)]
    [String]$LogFile = $Nothing 
  )
  Process {
    $Report = $Nothing
    # Получение объектов из папки профилей
    $SubDirs= Get-ChildItem $ProfileFolder -Force
    # Обработка объектов из папки профилей
    ForEach ($Dir in $SubDirs) {
      $LogMessage = $Nothing
      # Проверка, что объект существуюет и это папка
      If (Test-Path $Dir.FullName -PathType Container) {
        # Проверка, что профиль - это не служебная папка
        $NotDeleteFlag = $False
        ForEach ($ExcludedProfile in $ExcludedProfiles) {
          If ($Dir.FullName -eq $ExcludedProfile) {
            $NotDeleteFlag = $True
          }
        }
        # Removing the profile
        If ($NotDeleteFlag -eq $False) {
          $LogMessage = get-date -uformat "%d.%m.%Y %H:%M:%S"
          $LogMessage += "`t"
          $LogMessage += "Delete " + $Dir.FullName + "."
          # Executing removing process
          $CmdLine = "CMD /c RD /S /Q """ + $Dir.FullName + """"
          Invoke-Expression -command $CmdLine
        } Else {
          $LogMessage = get-date -uformat "%d.%m.%Y %H:%M:%S"
          $LogMessage += "`t"
          $LogMessage += "Skip " + $Dir.FullName + " - service folder."
        }
      } Else {
        $LogMessage = get-date -uformat "%d.%m.%Y %H:%M:%S"
        $LogMessage += "`t"
        $LogMessage += "Skip " + $Dir.FullName + " - file."
      }
      If ($LogFile.Length -gt 0) {
        $LogMessage | Out-File -FilePath $LogFile -Append
      } else {
        Write-Output $LogMessage
      }
    }
  }
}#End Function

Function Process-DeleteProfileRegistry {
  Param(
    [CmdletBinding()] 
    [Parameter(ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true,Mandatory=$true)]
    [String]$ProfileRegistrySection,
    [Parameter(ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true,Mandatory=$true)]
    [String[]]$ExcludedRegistrySections,
    [Parameter(ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true,Mandatory=$false)]
    [String]$LogFile = $Nothing
  )
  Process {
    $Report = $Nothing
    # Convering the registry path to Powershell path
    $ProfileRegistrySection = $ProfileRegistrySection.Replace("HKEY_LOCAL_MACHINE","HKLM:")
    # Requesting profile sections from the registry
    $Sections= Get-ChildItem $ProfileRegistrySection
    ForEach ($Section in $Sections) {
      $LogMessage = $Nothing
      # Filtering exclusions
      $NotDeleteFlag = $False
      ForEach ($ExcludedRegistrySection in $ExcludedRegistrySections) {
        If ($Section.Name.Contains($ExcludedRegistrySection)) {
          $NotDeleteFlag = $True
        }
      }
      # Removing profile sections
      If ($NotDeleteFlag -eq $False) {
        $LogMessage = get-date -uformat "%d.%m.%Y %H:%M:%S"
        $LogMessage += "`t"
        $LogMessage += "Delete " + $Section.Name + "."
        # Processing deleting the profile section
        $SectionPath = $Section.Name.Replace("HKEY_LOCAL_MACHINE","HKLM:")
        Remove-Item -Path $SectionPath -Force -Recurse
      } Else {
        $LogMessage = get-date -uformat "%d.%m.%Y %H:%M:%S"
        $LogMessage += "`t"
        $LogMessage += "Skip " + $Section.Name + " - service profile."
      }
      If ($LogFile.Length -gt 0) {
        $LogMessage | Out-File -FilePath $LogFile -Append
      } Else {
       # Write-Output $LogMessage
      }
    }
  }
}#End Function

Function Process-ResetPrintSpooler {
  Param(
    [CmdletBinding()] 
    [Parameter(ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true,Mandatory=$false)]
    [String]$LogFile = $Nothing
  )
  Process {
    $LogMessage = $Nothing
    $Error.Clear()
    # Остановка сервиса
    Try {
      Stop-Service -Name "Spooler" -Force
      $LogMessage += (get-date -uformat "%d.%m.%Y %H:%M:%S") + "`t" + 
        "Print Spooler service is stopped." + "`r`n"
    } Catch {
      $LogMessage += (get-date -uformat "%d.%m.%Y %H:%M:%S") + "`t" + $Error + "`r`n"
    }
    # Очистка заданий
    Try {
        Remove-Item -Path "C:\Windows\System32\spool\PRINTERS\*.*" -Force
        $LogMessage += (get-date -uformat "%d.%m.%Y %H:%M:%S") + "`t" + "Print queue is purged." + "`r`n"
    }
    Catch {
        $LogMessage += (get-date -uformat "%d.%m.%Y %H:%M:%S") + "`t" + $Error + "`r`n"
    }
    # Очистка неактивных портов принтеров терминального сервера
    $CmdLine = $ScriptTools + "DeleteInactivePortSilently.exe"
    $LogMessage += (get-date -uformat "%d.%m.%Y %H:%M:%S") + "`t" + 
      "Delete Inactive TS Ports by DeleteInactivePortSilently.exe command." + "`r`n"
    $CommandOutput = Invoke-Expression -command $CmdLine
    $CommandOutput | ForEach-Object {
      If ($_.Length -gt 0) {
        $LogMessage += (get-date -uformat "%d.%m.%Y %H:%M:%S") + "`t" + $_ + "`r`n"
      }
    }
    # Запуск сервиса
    Try {
      Start-Service -Name "Spooler"
      $LogMessage += (get-date -uformat "%d.%m.%Y %H:%M:%S") + "`t" + "Print Spooler service is started."
    } Catch {
      $LogMessage += (get-date -uformat "%d.%m.%Y %H:%M:%S") + "`t" + $Error
    }
    # Запись события в лог-файл
    If ($LogFile.Length -gt 0) {
      $LogMessage | Out-File -FilePath $LogFile -Append
    } Else {
      Write-Output $LogMessage
    }
  }
}#End Function

Function Get-Monday {
  Param(
    [CmdletBinding()] 
    [Parameter(ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
    [DateTime]$DateValue
  )
  Process {
    For ($i=0; $i -le 6; $i++) {
      If (($DateValue).adddays(-$i).DayOfWeek -eq "Monday") {
        Return ($DateValue).adddays(-$i)
      }
    }
  }
}#End Function

# --- Start ---

$ScriptFolder = $MyInvocation.MyCommand.Path.SubString(0,($MyInvocation.MyCommand.Path.Length - `
  $MyInvocation.MyCommand.Name.Length))
$LogFile = $ScriptFolder + 'Logs\' + (Get-Date -format yyyy_MM_dd) + "_" + `
  $MyInvocation.MyCommand.Name.SubString(0,($MyInvocation.MyCommand.Name.Length - 4)) + ".log"
$OSName = (Get-WmiObject -class Win32_OperatingSystem).Caption
$MondayDate = Get-Monday (Get-Date)
$CurrentDate = Get-Date
$DomainRDSGroupObject = [ADSI]"WinNT://glaverbel.com/$DomainRDSGroup,group"
$LocalRDSGroupObject = [ADSI]"WinNT://./$LocalRDSGroup,group"

# Adding local accounts SIDs to exclusions
$LocalAccounts = Get-WmiObject Win32_UserAccount -Filter "Domain='$env:computername'"
ForEach ($LocalAccount in $LocalAccounts) {
  $CurrentSID = $LocalAccount.SID
  If ($CurrentSID -ne $Nothing) {
    [void] $ExcludedRegistrySections.Add( $CurrentSID )
  }
}

# Adding local accounts Profiles paths to exclusions
ForEach ($CurrentSID in $ExcludedRegistrySections) {
  $CurrentPath =  (Get-WmiObject Win32_UserProfile -Filter "SID='$CurrentSID'").LocalPath
  If ($CurrentPath -ne $Nothing) {
    [void] $ExcludedProfilePaths.Add( $CurrentPath )
  }
}

# Log-file creation
If (-not(Test-Path ($ScriptFolder + 'Logs') -PathType Container )) {
  New-Item -ItemType Directory -Path ($ScriptFolder + 'Logs')
}
Out-File -FilePath $LogFile

# Check OS version
If (-Not $OSName.Contains("2012")) {
  Write-ScriptLog -LogFile $LogFile -Message ("The operating system is not match for this script.")
  Exit
}

# Extracting additional files
$ScriptTools = $ScriptFolder + `
  $MyInvocation.MyCommand.Name.SubString(0,($MyInvocation.MyCommand.Name.Length - 4)) + "\"
$ScriptToolsArchive = $ScriptFolder + `
  $MyInvocation.MyCommand.Name.SubString(0,($MyInvocation.MyCommand.Name.Length - 4)) + ".zip"
If (Test-Path $ScriptToolsArchive -PathType Leaf) {
  Write-ScriptLog -LogFile $LogFile -Message ("Script tools are found in " + `
    $ScriptToolsArchive + " archive.")
  Write-ScriptLog -LogFile $LogFile -Message ("Extracting the archive into " + $ScriptTools +".")
  
  New-Item -Path $ScriptTools -ItemType directory -Force
  $Shell = New-Object -com shell.application
  $ArchiveItem = $shell.NameSpace($ScriptToolsArchive)
  ForEach($Item in $ArchiveItem.items()) {
    $Shell.Namespace($ScriptTools).copyhere($Item, 0x14)
  }

  If (Test-Path $ZipArhiverPath -PathType Leaf) {
    [String]$CmdLine = $ZipArhiverPath
    [Array]$CmdLineArg = 'x', """$ScriptToolsArchive""",  "-o""$ScriptTools""", "-y"
    # Оператор & (ampersand) указывает, то необходимо выполнить внешнюю команду, указанную после него
    # подробнее тут https://technet.microsoft.com/en-us/library/ee176880.aspx
    Write-ScriptLog -LogFile $LogFile -Message (&$CmdLine $CmdLineArg)
  } Else {
    Write-ScriptLog -LogFile $LogFile -Message ("C:\Scripts\7za.exe is not found. " + `
      "Script execution can't be continue.")
    Exit
  }
}  


# Forbiging users for loging  - Removing users from Remote Desktop group
Write-ScriptLog -LogFile $LogFile -Message ("---   Closing the server for users   ---")
$LocalRDSGroupObject.Remove($DomainRDSGroupObject.Path)
Write-ScriptLog -LogFile $LogFile -Message ($DomainRDSGroupObject.Name.ToString() + `
  " is removed from " + $LocalRDSGroupObject.Name.ToString() + ".")
Write-ScriptLog -LogFile $LogFile -Message ("The server is closed for users.")

# Logging off active users
$Sessions = Get-ComputerSessions -Computer localhost
ForEach ($Session in $Sessions) {
  logoff $Session.Id /server:localhost
  Write-ScriptLog -LogFile $LogFile -Message ($Session.UserName + " (" + $Session.Id + `
    ") forced to logoff from the server.")
}

# Waiting for 30 seconds
Write-ScriptLog -LogFile $LogFile -Message ("Waiting 30 seconds for users logoff.")
Start-Sleep -s 30

# Check if there are no users sessions
$Sessions = Get-ComputerSessions -Computer localhost
If ($Sessions.Count -gt 0) {

  Write-ScriptLog -LogFile $LogFile -Message ("Currently logged on users number is " + `
    $Sessions.Count + ".")
  $ActiveSessions = "`r`n"
  $ActiveSessions += $Sessions | FT ID, UserName, State, IdleTime, LogonTime
  Write-ScriptLog -LogFile $LogFile -Message ($ActiveSessions)
  Write-ScriptLog -LogFile $LogFile -Message ("The script processing is terminated, " + `
    "please reboot the server to release sessions.")

} Else {

# Executing cleanup
  Write-ScriptLog -LogFile $LogFile -Message ("Currently logged on users number is 0.")
  # Profiles cleanup
  Write-ScriptLog -LogFile $LogFile -Message ("---   Profiles cleanup   ---")
  Process-DeleteProfileFolders -ProfileFolder $ProfileFolder `
     -ExcludedProfiles $ExcludedProfilePaths -LogFile $LogFile
  # Registry cleanup
  Write-ScriptLog -LogFile $LogFile -Message ("---   Registry cleanup   ---")
  Process-DeleteProfileRegistry -ProfileRegistrySection $ProfileRegistrySection `
    -ExcludedRegistrySections $ExcludedRegistrySections -LogFile $LogFile
  # Task scheduler cleanup
  Write-ScriptLog -LogFile $LogFile -Message ("---   Task scheduler cleanup   ---")
  Write-ScriptLog -LogFile $LogFile -Message ("Delete ""Optimize Start Menu Cache Files"" scheduled tasks.")
  Get-ScheduledTask | Where {$_.taskname -like "Optimize Start Menu Cache Files*"} | `
    Unregister-ScheduledTask -Confirm:$false
  # Print prooler cleanup
  Write-ScriptLog -LogFile $LogFile -Message ("---   Print prooler cleanup   ---")
  Process-ResetPrintSpooler -LogFile $LogFile
}

# Allowing users for loging  - Adding users to Remote Desktop group
Write-ScriptLog -LogFile $LogFile -Message ("---   Openning the server for users   ---")
$LocalRDSGroupObject.Add($DomainRDSGroupObject.Path)
Write-ScriptLog -LogFile $LogFile -Message ($DomainRDSGroupObject.Name.ToString() + " is added to " + 
  $LocalRDSGroupObject.Name.ToString() + ".")
Write-ScriptLog -LogFile $LogFile -Message ("The server is opened for users.")

# Remove auxiliary files
$CmdLine = "CMD /c RD /S /Q """ + $ScriptTools + """"
Invoke-Expression -command $CmdLine
Write-ScriptLog -LogFile $LogFile -Message ("$ScriptTools auxiliary folder is deleted.")

#Reboot server
If ($CurrentDate.Date -eq $MondayDate.Date) {
  Write-ScriptLog -LogFile $LogFile -Message ("Today is Monday - rebooting the server.")
  Restart-Computer -ComputerName "localhost"
}

Указанный скрипт рекомендуется поставить в планировщик Windows на ежедневное выполнение в ночные часы, когда на серверах нет пользователей. Запуск задания нужно выполнять от имени системы (от имени пользователя SYSTEM).

 

 

Вверх