Exchange 2013 uuenduste paigaldus

Sai hiljuti just läbi tehtud Exchange 2013 serverile uuenduste paigaldus.  Ja pärast seda olen veendunud, et ainuke õige viis Exchangele uuenduste pealepanekuks on olemasoleva serveri kõrvale uue paigaldamine ning rollide ületõstmine.  Sest nii hoiab kõvasti närve ja tõenäoliselt ka aega kokku.  Ning lisaks muudele eelistele saab uuendamist teha rahulikult keset päeva.

Exchange on juba pikka aega olnud selline keskkond, kus parandused lastakse välja ühe komplektina.  Ning Exchange 2013 puhul on seesama komplekt sobiv ka uue serveri paigalduseks.  Aga kui Sa järele proovid, siis tuleb välja, et testitud on ainult tühja masinasse Exchange paigaldust.

Mis siis valesti on?  Paigaldusprotsess on jagatud mitmeks sammuks.  Iga samm paigaldab/uuendab ühe komponendi.  Uuendamise puhul võetakse sammu alguses ports teenuseid (sealhulgas Exchange teenused), pannakse need seisma ja keelatakse nende käivitamine.  Kuskil paigalduse käigus aga läheb neid teenuseid järsku vaja ning teenused käivitatakse uuesti.  Windowsi teenustega on tavaliselt kõik hästi, ent Exchange enda teenuste puhul on paigaldusprotsess unustanud, et teenused keelati ära.  Ja tulemuseks on veateade ning poolik paigaldus.

Mis halvasti, see uuesti.  Aga tulemuseks on sama probleem, kuna paigalduse sammu alguses keelatakse teenused ning töö käigus püütakse neid uuesti käivitada.  Ja seda hoolimata sellest, et antud sammu on juba korduvalt jooksutatud…

See probleem pole Exchange 2013 Cumulative Update 10 juures unikaalne, vaid eksisteerib juba mitu aastat.  Ning kuskilt otsast pole näha, et midagi muutuks.

Kui ülaltoodud jutt pole Sind veel piisavalt ära hirmutanud ja Sa siiski proovid, siis varu kannatust ning ära mine arvutist kaugele, sest paigaldusprotsessi tuleb pidevalt järele aidata.

Enne alustamist tasuks salvestada hetke teenuste häälestus.  Seda saab teha näiteks Powershelliga:

Get-CimInstance -ClassName Win32_Service |
  Select-Object Name, StartMode , State |
  Export-Csv -Path .\teenused.csv -UseCulture -Encoding Default

Kui jooksva sammu progress on jõudnud ~3% peale, siis tuleks (tavalises) Powershellis käivitada järgmised käsud:

Get-Service -Name msexchange*, fsm |
  Set-Service -StartupType Manual

Ja sedasi iga kord, kui paigaldus uue sammu peale läheb.  Seetõttu ei tohigi masinast kaugele minna, sest teenused tuleb uuesti käivituvaks teha, muidu jääb paigaldus pooleli. Ja tuleb jälle kõik sammud läbi käia…

Kui nüüd paigaldus on lõppenud, siis on kogu selle sebimise käigus läinud kaduma teenuste varasem staatus.  Seetõttu tuleb see ise taastada:

$teenused = Import-Csv .\teenused.csv -UseCulture

$teenused | Where-Object StartMode -Like "Auto" |
  ForEach-Object {
    Set-Service -Name $_.name -StartupType $_.StartMode
  }

$teenused | Where-Object State -Like "Running" |
  Start-Service

Tegelikult salvestab Exchange paigaldus samuti teenuste häälestuse.  Ent miskipärast häälestuse taastamisega paigalduse lõpus enam hakkama ei saadud.

Advertisements

SSH tugi Powershellis

Powershelli tootetiim andis teada, et nad alustasid SSH protokolli toe planeerimist Powershellile.  Seda siis muuseas ka sedapidi, et eemalt masinast saab teha SSH ühenduse ja sihtpunktis kasutada Powershelli.

Teistpidi lahendusi (Powershell teeb SSH sessiooni teise masinasse) on juba mõnejagu olemas.  Näiteks võiks mainida moodulit Posh-SSH.  Kommertstoodetest võiks mainida PowershellServerit, millest on tasuta personaalne versioon olemas.  Samuti võib võtta suvalise Windowsi peal töötava SSH serveri ja kasutada selle sees käsurea interpretaatoriks Powershelli.

Nimetatud funktsionaalsust on korduvalt Powershelli tagasiside veebis küsitud.

Seisuga Oktoober 2015 on valmis saadud OpenSSH for Windows, mis on esialgu veel arendamisel, ent tulemus lubatakse valmis saada järgmise aasta esimeses pooles.

Excel, CSV ja Powershell

Aeg-ajalt on meil vaja tegeleda CSV failidega.  See lühend peaks tähendama Comma Separated Value ent Excelil on sellest oma arusaam.  Nimelt kasutab Excel regiooni seadetest loendieraldajat, ning Eesti seadete korral on selleks koma asemel semikoolon.  Ja kui Excelis teha valmis kasutajate nimikiri, kellele oleks vaja teha kasutajakonto ja postkast, siis on CSV-failiks salvestamise tulemus järgmine

Eesnimi;Perenimi;osakond
Mati;Mõisnik;Müük
Kätlin;Pöögelman;Arendus

Ja kui seda faili püüda Powershellis importida, siis tekib kaks probleemi:

  1. Import-CSV eeldab, et CSV fail on salvestatud ASCII kodeeringus (ehk siis täpitähed on paigast ära)
  2. semikoolon on Import-CSV jaoks vale eraldaja

Esimese probleemi vastu on lihtne lahendus: Get-Content oskab faili sisu õiges kodeeringus kätte saada.  Ainult et Get-Content annab meile faili tekstistringidena.  Õnneks on meil olemas käsk ConvertFrom-CSV:

#Requires -Version 3

Get-Content .\Vihik1.csv | ConvertFrom-Csv -UseCulture

Käsurea parameeter UseCulture hoolitseb selle eest, et kasutataks õiget loendieraldajat.

Sarnane probleem tekib meil ka siis, kui oleks vaja näiteks kasutajakonto info eksportida Excelisse:

#Requires -Version 3

Get-ADUser -Filter  {(Enabled -eq $true) -and (City -like "Tallinn")} `
    -Properties mail |
  Select-Object *name, mail |
  ConvertTo-Csv -UseCulture -NoTypeInformation|
  Out-File kasutajad.csv

Aga ehk saaks ka Import-CSV ja Export-CSV õigesti tööle?  Tegelikult saabki:

#Requires -Version 3
import-csv .\Vihik1.csv -Encoding Default -UseCulture

Get-ADUser -Filter  {(Enabled -eq $true) -and (City -like "Tallinn")} `
    -Properties mail |
  Select-Object *name, mail |
  Export-Csv kasutajad.csv -Encoding Default -UseCulture

Häda on vaid selles, et kui nüüd sama skript viia mõnda teise riiki, siis tuleks kasutada teist loendieraldajat.  Õnneks on loendieraldajat võimalik süsteemi käest küsida:

Get-Culture
(Get-Culture).TextInfo
(Get-Culture).TextInfo.ListSeparator

(Get-Culture).GetType().FullName
  # Powershell ISE korral
(Get-Culture).GetType().BaseType.FullName

Tuleb vaid meeles pidada, et loendieraldaja on kasutajapõhine seadistus ja kui CSV fail läheb teise inimese kätte või tuleb teise inimese käest, siis tuleb teada tema keele-eelistusi:

$kultuur = [System.Globalization.CultureInfo]"en-us"
$eraldaja = $kultuur.TextInfo.ListSeparator
Import-Csv .\Vihik1.csv -Encoding Default -Delimiter $eraldaja

Mis mälu mul masinas/serveris on

Aeg-ajalt tekib küsimus, et masinas on Windowsi (ja minu) teada miski hulk mälu, aga kui suured ja kui palju mooduleid masinas on ja kas midagi annab veel juurde panna, pole teada.  Või äkki tuleb hoopis olemasolevad moodulid välja vahetada?

Siin aitab vana hea WMI.  Nimelt annab selle käest küsida, et palju mälu kontroller üldse toetab ning palju emaplaadil pesasid on:

Get-WmiObject Win32_PhysicalMemoryArray

Siin tuleb muidugi teada, et atribuut MaxCapacity on kilobaitides. Seega võiks tulemust loetavuse huvides natuke parandada.

$MaxCapacity = @{name="Max memory (MB)"; expression={$_.MaxCapacity/1KB}}

Get-WmiObject Win32_PhysicalMemoryArray |
  Format-Table $MaxCapacity, MemoryDevices –AutoSize

Siit saime kätte maksimaalse mälu hulga ja pesade arvu, aga mis ühikutes mälu praegu masinas on?

Get-WmiObject Win32_PhysicalMemory

Jällegi oleks hea tulemus natuke loetavamaks teha:

$capacity = @{name="Capacity (MB)"; expression={$_.Capacity/1MB}}

Get-WmiObject Win32_PhysicalMemory |
  Format-Table DeviceLocator, $capacity, speed –AutoSize

Kõik ülaltoodu töötab lokaalses masinas, aga kui oleks vaja sama info saada kätte eemalt?  WMI liides on ka eemalt kättesaadav, vaja vaid läheneda kasutajana, kellel on lubatud WMI-liidese poole pöörduda:

$me = Get-Credential domain\kasutaja

Get-WmiObject Win32_PhysicalMemoryArray –ComputerName masin –Credential $me |
  Format-Table $MaxCapacity, MemoryDevices –AutoSize

Get-WmiObject Win32_PhysicalMemory –ComputerName masin –Credential $me |
  Format-Table DeviceLocator, $capacity, speed –AutoSize

Ainult et nüüd on meil probleem.  Iga Get-WmiObject käsk loob eemal olevasse masinasse ühenduse, saab info ja paneb ühenduse uuesti kinni.  Lisaks tuleb eraldi oodata iga masina taga, millega soovitakse ühendust saada.  Ja kui minu ning eemalasuva masina vahel on tulemüür, siis võib seal WMI jaoks vajalikud pordid kinni olla.  Ning nende lahtitegemine on natuke problemaatiline (WMI kasutab DCOM-liidest ehk dünaamilisi porte).

Antud situatsioonile on kaks võimalikku lahendust.  Esimeseks oleks kasutada Powershelli kaugühendusi.  Võrgutulemüürides on vaja avada vaid üks port (tcp/5985) ja sihtmasinas saaks ligipääsu lubada ka mitte-adminkontole:

#Requires -Version 2
$session = New-PSSession –Computername masin1, masin2 –Credential $me

Invoke-Command –Session $session { Get-WmiObject Win32_PhysicalMemoryArray} |
  Format-Table PSComputername, $MaxCapacity, MemoryDevices –AutoSize

Invoke-Command –Session $session { Get-WmiObject Win32_PhysicalMemory} |
  Format-Table PSComputername, DeviceLocator, $capacity, speed –AutoSize

Remove-PSSession $session

Windows 8/Server 2012 (ja värskemad) võimadavad aga veelgi lihtsamat lähenemist.  Nimelt on seal Powershelli sisse tehtud WMI alternatiivliides, mis samuti kasutab sessioone: CIM.  Sessioonid muide on samad, mis Powershelli kaugühenduse puhulgi.

#Requires -Version 3
#Requires -Modules CimCmdlets
$session = New-CimSession -ComputerName masin1, masin2 -Credential $me

Get-CimInstance Win32_PhysicalMemoryArray -CimSession $session |
  Select-Object  PsComputerName, $MaxCapacity, MemoryDevices |
  Format-Table –AutoSize

Get-CimInstance Win32_PhysicalMemory -CimSession $session |
  Sort-Object PsComputerName |
  Select-Object PsComputerName, DeviceLocator, $capacity, speed |
  Format-Table –AutoSize

Remove-CimSession $session

Üllatuste vältimiseks tasub veel ära mainida, et ülaltoodud tehnika abil saab kätte mitte ainult süsteemimälu, vaid võib leida ka muud, nagu näiteks videomälu jms.

Kontode muutmine domeenis

Vahel on vaja korraga paljusid domeenikontosid muuta.  Näiteks vahetus osakonna juht ja nüüd oleks vaja kõikide osakonna töötajate kontodel juhi atribuut ära muuta:

#Requires -Version 3.0
#Requires -Modules ActiveDirectory

$vana = Get-ADUser "Mihkel Metsik"
$uus = Get-ADUser "Leila Liblikas"

Get-ADUser -Filter {Manager -eq $vana.DistinguishedName} |
  Set-ADUser -Manager $uus

Tasub tähele panna, et kasutajate otsimisel tuleb ette anda konto atribuut DistingushedName, aga uue juhi määramisel on vaja juhi kasutajakontot.

Natuke keerulisemaks läheb asi siis, kui muutmise käigus tuleb igale kontol midagi unikaalset muuta.  Näiteks võib juhtuda, et meil tuleb muuta ära kasutaja atibuut DisplayName.  Kui seni oli see kujul “eesnimi perenimi”, siis nüüd tahaks seda kujul “perenimi, eesnimi”.  Ja teeme seda ainult Hiina töötajatega.  Antud juhul ei saa enam nii lihtsalt läbi kui eelmisel korral:

Get-ADUser -Filter {country -eq "cn"} |
  ForEach-Object {
    Set-ADUser -Identity $_.sid -DisplayName (
      "{1}, {0}" -f $_.GivenName, $_.SurName
    )
  }

Natuke rohkem tööd on ka siis, kui me peaksime muutma kasutajakontode logon nimesid.  Koostame logon nime nii, et võtame eesnimest 5 tähte ja perenimest 2.  Ning võtame seekord ainult ühe grupi liikmed.

Grupi liikmed võivad olla nii kasutajad, arvutid, kui ka teised grupid.  Meie aga tahame vaid kasutajaid.  Lisaks on veel probleem sellega, et Get-ADGroupMember tagastab vale tüüpi objekti, millel pole ees- ja perenime küljes:

Get-ADGroupMember "meie" |
  Where-Object {$_.objectClass -eq "user"} |
  Get-ADUser |
  ForEach-Object {
    $uusnimi = (
      "{0}{1}" -f $_.GivenName.Substring(0,5), $_.SurName.Substring(0,2)
    )
    if (-not (Get-ADUser $uusnimi)) {
      Set-ADUser -Identity $_.sid -SamAccountName $uusnimi
    }
  }

Kontonimede muutmisel tuleb arvestada, et atribuudil SamAccountName on peal 20 märgi piirang ja pikema teksti sisestamiskatsel saame veateate. Samuti tuleks arvestada, et mainitud atribuut peab olema domeeni piires unikaalne ja seetõttu kontrollime, et sellise nimega kasutajat juba olemas ei oleks. Jääb veel võimalus, et sama nime kannab mõni arvuti või grupp, aga seda me siin ei kontrolli. Samuti jääb juba sellise nimega kasutaja olemasolu korral kasutaja logon nimi muutmata.

Juhul kui iga muudetud väärtus on ette teada ja seda ei saa välja arvutada, siis aitab hädast välja juba varem kirjeldatud CSV faili import.  Vaja vaid, et CSV failis olesid õigete nimedega veerud:

Import-Csv c:\modify.csv |
  ForEach-Object {
    Set-ADUser -Identity $_.id -MobilePhone $_.mobile
  }

Ühe liigutuse võiks veel teha.  Nimelt kui meil on rühmapoliitika korralikult juurutatud, siis ei ole kasutajatel enam vaja personaalseid logon skripte.  Korjame need ära:

Get-ADUser -Filter * |
  Set-ADUser -Clear ScriptPath

Kui ülaltoodud näidetest jääb väheks, siis TechNetis on näiteid veel.  Need on küll pärit Server 2008 R2 ajast, aga töötavad ka uuemate ActiveDirectory moodulitega.

NTFS Alternate Data Stream

NTFS failisüsteemis on pikka aega olnud funktsionaalsus, millel ei ole otseselt kasutajaliidest ning mida tavaliselt kasutatakse varjatult.  See on Alternate Data Stream, ehk siis funktsioon, mis laseb failil omada mitut erinevat sisu.

Kui see alternatiivne sisu on tekst, siis saab seda avada Notepad’i abil.  SysInternals Toolkit sisaldab utiliiti Streams, mis näitab (ja laseb kustuta) faili küljes olevaid erinevaid sisusid.

Powershell 3.0 toetab samuti seda funktsionaalsust.  Nimelt on hulk käske, millel on (dokumenteerimata) käsurea võti –Stream.  Vastavad käsud leiab lihtsalt:

#Requires –Version 3.0

Get-Command –ParameterName stream -Module Microsoft.PowerShell.Management

Tegelikult tegeleb veel üks käsk alternatiivsisudega, või õigemini ühe konkreetse sisuga.  See käsk on Unblock-File, mis eemaldab faili küljest märke, et see on Internetist saabunud.

Ülalmainitud käskudega saab näiteks vaadata, millistel failidel on alternatiivsisu:

Get-ChildItem |
  Get-Item -Stream * |
  Group-Object stream

Nii saab teada failides olemasolevate alternatiivsisude arvud.  Pärast seda oskame juba tahta konkreetse nimega alterantiivsisu:

Get-ChildItem |
  Get-Item -Stream * |
  Where-Object {$_.Stream -like "Zone.Identifier" }

nüüd teame juba, et millistel failidel on meid huvitava nimega alternatiivsisu ning siis saab nendega midagi ette võtta.  Näiteks võime teksti tüüpi sisu ekraanile manada ehk siis teise faili kopeerida. Või siis hoopis ära kustutada:

Get-Content .\minufail.pdf -Stream Zone.Identifier
  # või siis ka:
Get-Content .\minufail.pdf:Zone.Identifier

Get-Content .\minufail.pdf -Stream peidus |
  Set-Content .\teine.txt -Stream uus

Remove-Item .\katse.txt -Stream Zone.Identifier
  # või antud juhul ka:
Unblock-File .\katse.txt

kuna *-Content käsud eeldavad, et sisu on tekst (ridade kollektsioon), siis ei saa näiteks linkide lemmikuikoone (favicon) sedasi kopeerida/luua.  Nii et peamiselt saame me ikka eemaldada skriptidelt fakti, et need internetist alla tõmmati.  Või siis otsida, et ega mõni pahavara pole end alternatiivsisusse peitnud.

Kasutajakontode loomine domeenis

Tahan tuua mõned näited, kuidas domeenis objekte hallata skriptide abil.  Tasub vist mainida, et minu näidete töötamise eelduseks on Windows Server 2008 R2 (või 2012) haldusvahenditega kaasatuleva Powershelli mooduli ActiveDirectory olemasolu.

Alustame kasutajakonto loomisest:

#Requires –Version 2.0
#Requires –Modules ActiveDirectory

# laeme vajadusel AD mooduli
if ($Host.Version.Major -eq 2) {
  Import-Module ActiveDirectory
}

New-ADUser –Name Mari

Kogu töö teeb ära käsk New-ADUser ning uue konto loomiseks rohkem polegi vaja.  Tuleb lihtsalt tähele panna, et kuna kontole ei määratud parooli, siis on konto välja lülitatud ja seda ei saa sisse lülitada enne, kui kontole on määratud parool.  Lisaks on nii logon nimi (sAMAccountName) kui ka konto nimi mõlemad samad.  Ning ees- ja perenime pole üldse.  Selle vea saab kohe parandada:

$userParams = @{
  Name = "Kati Kallike"
  SamAccountName = "Kati"
  GivenName = "Kati"
  SurName = "Kallike"
}

New-ADUser @userParams

Kui me juba skripte teeme, siis tõenäoliselt on meil korraga rohkem kasutajaid, keda luua ning üks lihtsamaid viise kasutajaid sedasi teha on CSV faili import.  Teeme näiteks järgmise CSV faili:

GivenName,Surname,Department,SamAccountName
Mati,Maasikas,Müük,mati
Juhan,Juurikas,Juhatus,juhan

ja impordime selle:

Import-Csv c:\users.csv -Encoding Default |
  select *,
    @{name="Name"; e={"{0} {1}" –f $_.GivenName, $_.SurName }},
    @{name="DisplayName"; e={"{0} {1}" –f $_.GivenName, $_.SurName }} |
  New-ADUser

Töö on lihtne selle tõttu, et CSV failis on veergude nimed viidud kooskõlla käsu New-ADUser parameetritega (ja tuletatud pikk nimi).

Teine võimalus uuele kontole atribuute ette anda on kasutada valmis kontot kui malli.  Selleks tuleb käsule New-ADUser anda ette kasutajakonto, mille küljest olemasolevad atribuudid võtta:

$template = Get-ADUser _usertemplate -Properties City, ProfilePath, MemberOf

Import-Csv c:\users.csv |
  New-ADUser -Instance $template

Nii täidetakse atribuudid, mis sageli on erinevatel kontodel ühised.  Muuhulgas ka gruppidesse kuulumised.  Ent kuna kasutajale pole antud parooli, siis on kasutajakontod ikka veel välja lülitatud.  Parooli lisamiseks on mitu võimalust.

Parooli saab skripti käivitaja käest küsida:

#Requires –Version 2.0
Read-Host -AsSecureString -Prompt "Ütle üks ilus parool"

(Get-Credential -Message "Ütle üks ilus parool" -UserName kasutaja).Password

Samas saab parooli ka näiteks tekstifailist (või parooligeneraatorist) lugeda:

ConvertTo-SecureString -String (Get-Content c:\parool.txt) -AsPlainText -Force

Paneme parooli lisamise eelnevale skriptile otsa:

#Requires –Modules ActiveDirectory

Import-Csv c:\users.csv |
  New-ADUser -AccountPassword (Get-Credential -UserName kasutaja).Password -Enabled $true

Nii saab kasutajakontod ka kohe sisse lülitada.  Samas on võib-olla hea mõte enne kasutajale parooli määramist välja mõtelda, kuidas parool kasutajani toimetada.  Ja las ta niikaua olla välja lülitatud ning ilma paroolita.

Loome nüüd uued kasutajad kindlasse OU-sse ning paneme nad ka gruppi :

#Requires –Modules ActiveDirectory
$rootdn = (Get-ADDomain).DistinguishedName

Import-Csv c:\users.csv |
  New-ADUser -PassThru -Path "ou=uued,ou=kasutajad,$rootdn" |
  Add-ADPrincipalGroupMembership -MemberOf "kõik töötajad"

Kui me nüüd mõne aja pärast avastame, et mõned atribuudid jäid kohe paika panemata, siis tuleb seda teha hiljem. Ja käsk Set-ADUser ei ole enam nii vastutulelik ning ei ole nõus otse torust muudetavaid väärtusi lugema.  Lisaks ei ole kõikide kasutajakonto atribuutide jaoks ka eraldi käsureavõtit. Siin tuleb natuke kavaldada. Loome kõigepealt sobiva CSV faili:

id, email, parool
mati,"mati@firma.ee",Par0ol33
"cn=Juhan Juurikas,ou=kasutajad,dc=firma,dc=ee","juhan@firma.ee",pAro0like

ja siis anname selle käsule ette. Tasub tähele panna, et parameeter -Add lubab lisada mistahes konto atribuute – tuleb vaid teada nende atribuutide LDAP-nimesid (need erinevad graafilise kasutajaliideses ja Powershellis kasutatavastest nimedest):

Import-Csv c:\modify.csv | ForEach-Object {
  Set-ADUser -Identity $_.id -Add @{mail=$_.email}
}

Sama lugu on parooli tagantjärele muutmise ja kontode avamisega:

Import-Csv c:\modify.csv | ForEach-Object {
  Set-ADAccountPassword -Identity $_.id `
                        -Reset `
                        -NewPassword (ConvertTo-SecureString -AsPlainText -Force $_.parool) `
                        -PassThru |
  Enable-ADAccount
}

Käsurea atribuudi -Reset kasutamine eeldab, et skripti käivitaval kasutajakontol on õigus muuta teiste kasutajate paroole. Kui skriptiga tahetakse muuta kasutaja enda õigustes tema parooli, siis tuleb see asendada atribuudiga -OldPassword (ja lisada kehtiv parool).