Vanad kontod domeenis

Aeg-ajalt on domeenist vaja üles leida kasutajakontod, kes pole tükk aega ennast näole andnud.  On nad siis ettevõttest lahkunud või on nende projekt lõppenud ja neil pole enam ligipääsu vaja.  Tavaliselt unustatakse mõlemal juhul süsteemiülemale teatada, et konto võib kinni panna.

Sarnane probleem tekib ka arvutikontodega.  Kui arvutid maha kantakse ning ära müüakse, siis ununeb sageli masina domeenist väljavõtmine.  Aga ConfigMgr’i jaoks tähendab see terve hulk zombisid, mida ei ole enam vaja.

Alates Windows Server 2003-st on haldusvahendite hulgas ka hunnik käsurea utiliite, mis antud juhul abiks on:

dsquery user "ou=kasutajad,ou=firma,dc=firma,dc=ee" –inactive 13

dsquery user –stalepwd 90

Ülaltoodud näites esimene rida otsib välja kasutajakontod, mis pole viimase 13 nädala jooksul sisse loginud ning teine rida otsib välja kasutajad, mis viimase kolme kuu (90 päeva) jooksul pole parooli muutnud.  Sama saab teha ka arvutikontodega.

tuleb vaid tähele panna, et kui inimesele on tehtud konto ja sellega pole kunagi sisse logitud, siis ei leia –inactive võtmega otsimisel seda kontot üles.  Nimetatud võti kasutab domeenikonto atribuuti lastLogonTimestamp, kus on kirjas et millal konto viimati sisse logis.  Ja kui ta pole kunagi loginud, on see atribuut tühi.

Masinakontodega on natuke lihtsam, kuna masinad käivad regulaarselt iga 30 päeva tagant oma parooli muutmas.  Kui nad pole seda teinud, siis pole nad viimase kuu jooksul domeenikontrollerit näinud.

Kui nüüd kontod üles leitud, siis peaks need välja lülitama.  Siis on kindel, et need ei saa enam domeeni sisse ja ühtlasi tuleb sageli ka välja, et kui konto(d) on tegelikult vajalikud, siis võetakse meiega varsti ühendust:

dsquery user –inactive 16 | dsmod user –disabled yes

dsquery computer –stalepwd 90 | dsmove –newparent "ou=teadmata kadunud"

Sellisel viisil võib muidugi juhtuda, et osa leitud kontosid juba on välja lülitatud.  Seega oleks vaja natuke võimsamat vahendit.

Windows Server 2008 R2 ja tema kaughaldusvahendid sisaldavad Powershelli moodulit ActiveDirectory.  Antud juhul on see väga abiks.  Vaja vaid, et vähemalt üks domeenikontroller oleks Windows Server 2008 R2 või siis et vähemalt ühele domeenikontrollerile oleks paigaldatud Active Directory Gateway Management Service.  Ja siis saame teha järgmist:

#Requires -Modules ActiveDirectory

Search-ADAccount -AccountInactive -TimeSpan 90.00:00:00 -UsersOnly

$ammu = (Get-Date).AddDays(-90)

Get-ADUser -Filter {logonCount -ge 1 -and LastLogonDate -le $ammu} |
  Move-ADObject -TargetPath "ou=kadunud hinged"

Get-ADUser -Filter {Enabled -eq $true -and PasswordLastSet -le $ammu} `
  -SearchBase "ou=IT,ou=kasutajad,dc=firma,dc=ee"

Get-ADComputer -Filter {PasswordLastSet -le $ammu} | Disable-ADAccount

Ülaltoodud näites leiab 7. rida kasutajakontod, kes on vähemalt korra sisse loginud ja viimane sisselogimine on toimunud 90 päeva tagasi või varem.  Leitud kontod tõstetakse eraldi OU-sse.

10. rida leiab kasutajakontod kindlast OU-st, kes ei ole välja lülitatud ja kelle parool on viimati vahetatud 90 päeva tagasi või varem.

Viimane rida leiab arvutikontod mis ei ole viimase 90 päeva jooksul domeenikontrollerit näinud (kui oleks, siis oleks ka parool juba muudetud) ning lülitab need välja.

Loomulikult tekib küsimus, et milliseid atribuute veel on, mida kasutada saaks.  Küsime seda Powershelli käest:

#Requires -Modules ActiveDirectory

Get-ADUser -Identity mina -Properties *

Siin tuleb tähele panna, et üks tagastatud atribuut on selline, mis arvutatakse atribuudi nTSecurityDescriptor pealt: see on CannotChangePassword.  Seda atribuuti ei saa filtris kasutada, küll aga saab seda kontrollida mööda toru järgmises käsus:

Get-ADUser -Filter * -Properties CannotChangePassword |
  Where-Object {$_.CannotChangePassword}

Tulemuseks on kasutajate nimekiri, kes ei saa ise oma parooli muuta.

Täpsemalt infot filtri võimalustest ja atribuutidest, mida võib Active Directory objektide küljest leida, saab järgmistest Powershelli spikri teemadest (või veebist):

Advertisements

Kasutajaprofiilid ja Powershell

Viimasel ajal olen korduvalt kokku puutunud vajadusega tegeleda lokaalsete kasutajaprofiilidega Windowsi masinates.  Tavaliselt pakuvad huvi kaks teemat:

  • oleks vaja kasutajaprofiil uue konto kätte anda
  • tahaks kustutada ülearused kasutajaprofiilid

Esimest tegevust on sageli vaja, kui toimub Active Directory migratsioon ja kasutajakontod tõstetakse uude AD domeeni.  Siis saab selle tegevuse läbi viia kasutades tarkvara Active Directory Migration Tookit, kasutades Security Translation Wizard’it.

Teise tegevusega on natuke raskem.  Üks viis antud juhul tegutseda on avada My Computer Properties |  Advanced System Settings | User Profiles | Settings .  Avaneb aken, kus on kirjas kõik kasutajaprofiilid lokaalses masinas, nende maht ja viimati kasutamise kuupäev.  Samas võtab mahu arvutamine arvestatava hulga aega, nii et selle järel tuleb (vahel päris kaua) oodata.  Ja lisaks ei saa seal nimekirja sorteerida (see on vaikimisi sorteeritud kasutajakonto nime järgi).  Kui tahaks nimekirja potensiaalselt kustutamisele minevatest profiilidest kiiremini või mingite tingimuste järgi, siis võiks ju kasutada käsurida ja Powershelli.

WMI andmebaas sisaldab nimekirja kasutajaprofiilidest.  Selleks tuleb välja kutsuda WMI klass Win32_UserProfile:

get-wmiobject Win32_UserProfile

Kuna WMI klassidel on alati küljes ülearused atribuudid, siis võiks ekraanile manada ainult olulise info:

Get-WmiObject Win32_UserProfile |
  Sort-Object LastUseTime |
  Format-Table LastUseTime, RoamingConfigured, LocalPath, SID -AutoSize

Tulemuses on näha kasutajaprofiilid sorteerituna viimati kasutatud kuupäeva järgi (vanemad enne) koos faktiga, et kas profiil on Roaming Profile. Tasub tähele panna, et atribuudi LastUseTime sisu võõraste kasutakontode kohta näeb ainult admin-õigustes kasutaja. Puuduseks on see, et atribuut LastUseTime on WMI kuupäeva vormingus, mida Powershell ei suuda ise normaalseks kuupäevaks teisendada.  Õnneks on selle peale mõeldud ning WMI objektid tagastatakse koos lisatud meetodiga, mis oskab kuupäeva konvertida:

$lastused = @{name="last used"; expression={$_.ConvertToDateTime($_.LastUseTime)}}
$roaming  = @{name="Roaming?";  expression={$_.RoamingConfigured}}

Get-WmiObject Win32_UserProfile |
  Sort-Object LastUseTime -Descending |
  Format-Table $lastused, $roaming, LocalPath, SID –AutoSize

Roaming Profile peaks olema muuhulgas salvestatud ka serverisse, nii et neid võib rahulikumalt kustutada.  Edasi oleks vaja valida välja profiilid, mida on viimati kasutatud varem, kui ette antud hulk päevi (näiteks 60) tagasi.  Lisaks võiks välja jätta süsteemsed profiilid ja need, mis parajasti kasutusel on:

Get-WmiObject Win32_UserProfile |
  Where-Object {-not ($_.Loaded -or $_.Special) } |
  Where-Object { $_.ConvertToDateTime($_.LastUseTime) -le (Get-Date).AddDays(-60) } |
  Sort-Object LastUseTime |
  Format-Table $lastused, $roaming, LocalPath, SID –AutoSize

Kui tekib tahtmine näha SID-i asemel kasutajakontot, siis saab seda teha teise WMI klassiga:

Get-WmiObject Win32_Account -Filter ("sid='{0}'" -f $mysid)

Nüüd jääb veel üle sõelale jäänud profiilid ära kustutada:

Get-WmiObject Win32_UserProfile |
  Where-Object {-not $_.Loaded } |
  Where-Object { $_.ConvertToDateTime($_.LastUseTime) -le (Get-Date).AddDays(-60) } |
  Remove-WmiObject

Mõistlik oleks antud tegevuste jada vormistada funktsiooniks või skriptiks, et saaks mugavasti ette anda päevade arvu ja veel mõned parameetrid. Kui tahad alltoodud koodi salvestada skriptiks, siis jäta ära esimene ja viimane rida.

function Get-UserProfile {
    Param (
    	[String[]] $ComputerName = ".",
    	[int] $Days,
    	[ScriptBlock] $Filter,
    	[Switch] $Delete
    )

    $userProfiles = Get-WmiObject Win32_UserProfile -ComputerName $ComputerName
    if ($PSBoundParameters.Keys -contains "Filter") {
    	$userProfiles = $userProfiles |
    		Where-Object $Filter
    }
    if ($PSBoundParameters.Keys -contains "Days") {
    	$userProfiles = $userProfiles |
    		Where-Object { $_.ConvertToDateTime($_.LastUseTime) -le (Get-Date).AddDays(0 - $Days) }
    }

    if ($PSBoundParameters.Keys -contains "Delete") {
      $userProfiles | Remove-WmiObject
    } else {
    	$lastused = @{name="Last used"; expression={$_.ConvertToDateTime($_.LastUseTime)}}
    	$roaming = @{name="Roaming ?"; expression={$_.RoamingConfigured}}
    	$machine = @{name="Computer"; expression={$_.__Server}}
        $uName = @{name="User Account"
                   expression={Get-WmiObject Win32_Account -Filter ("sid='{0}'" -f $_.sid) |
                                 Select-Object -ExpandProperty caption}
        }

    	$userProfiles |
      	    Sort-Object LastUseTime -Descending |
      	    Format-Table $lastused, $roaming, $machine, LocalPath, $uName, SID -AutoSize
    }
}

Kui ülaltoodud funktsioon on laetud Powershelli sessiooni, siis saab seda kasutada nii profiilide nimekirja saamiseks, kui ka kustutamiseks. Kui vormistasid ülaltoodud koodi skriptiks, siis pead hoolitsema, et see skript ka üles leitaks (ja et signeerimata skriptide kasutamine oleks lubatud). Alltoodud näidete eelduseks on, et on laetud funktsioon või skript on sama nimega nagu funktsioon ja asub kaustas, kust Powershell skripte/programme otsib ($env:Path).

# get all roaming user profiles, which are located on server
Get-UserProfile -Filter {$_.RoamingPath -like "\\server\profiles*"}

# get user profiles last used 30 (or more) days ago
Get-UserProfile -Days 30

# get user profiles from several computers
Get-UserProfile -ComputerName server1, server2 -Days 60

# delete corrupted user profiles
Get-UserProfile -Filter {$_.Status -eq 3} -Delete

Tasub tähele panna, et ülaltoodud WMI klass toimib alates Windows Vista-st. Varasemate OS-ide jaoks on vaja alternatiivseid lahendusi. Nagu näiteks Server 2003 Resource Kit’i utiliit DELPROF.  Selle utiliidi jaoks pole vaja ülaltoodud võimlemist, kuna see utiliit oskab ise vanu profiile üles otsida:

delprof /q /i /d:60

Ainuke probleem selle tööriistaga on, et see on ametlikult toetatud vaid Windows 2000/XP või Server 2003 peal.  Windows Vista/Server 2008 ja värskemad pole toetatud.  Kui soovid ilma Powershellita hakkama saada, siis tuleb vaadata 3. partei tööriistade poole, nagu näiteks DelProf2. Selle käsurida sobib algse utiliidiga.

On veel võimalus kasutada VBScriptis kirjutatud skripti, kuigi ka selle puhul on kurdetud, et see ei tööta Windows 7 peal.

Kaustade õiguste kaardistus

Minult on korduvalt küsitud, et kuidas saaks failiserveri kaustade pealt kätte ülevaate NTFS ligipääsuõigustest.  Isegi nii korduvalt, et ma ei viitsi enam sama asja otsast peale leiutada (vahepeal jõuan tavaliselt lahenduse ära unustada).  Panen selle hoopis siia kirja.

Kogu ülesande saab kokku võtta käsu cacls kasutamisega.  Tuleb see vaid käivitada meid huvitava kaustahierarhia peal.

Järgnev skript töötab praktiliselt kõigi veel vähegi oluliste Windowsi versioonide peal.  Skripti tulemuseks on ekraanil nimekiri kaustasid ning iga kausta juures selle kausta ligipääsuõiguste komplekt:

@echo off

for /r %1 %%i in (.) do cacls "%%i"

Kui seda skripti hoolega vaadata, siis tööd teeb ainult üks rida.  Nii et seda võiks jooksutada ka otse käsurealt. Skripti puhul on lihtsalt mugavam tulemus faili saada. Eeldame, et ülaltoodud skript kannab nime GETACL.CMD. Siis annavad kaks järgmist käsku peaaegu sama tulemuse:

getacl d:\folder > permissions.txt

for /r d:\folder %i in (.) do cacls "%i" >> permissions.txt

Teise käsu tulemus lisab õiguste nimekirja faili, kui see on juhtumisi olemas. Esimene käsk kirjutab faili sisu üle.

Põhimõtteliselt on ülesanne suhteliselt sarnane varasemas artiklis kirjeldatuga.  Ainuke vahe on see, et nüüd protsessime kaustu, mitte faile.  Seetõttu võime sama asja teha ka kasutades utiliiti forfiles:

forfiles /p d:\folder /s /c "cmd /c if @isdir==TRUE cacls @file"

Või siis sama asi PowerShelliga.  Kasutame siin utiliidi cacls asemel alates Vistast seda asendavat utiliiti icacls:

Get-ChildItem -Path d:\folder -Recurse |
    Where-Object {$_.PSIsContainer} |
    Foreach-Object {icacls $_.FullName }

#Requires -Version 3
Get-ChildItem -Path d:\folder -Recurse -Directory |
  Foreach-Object {icacls $_.FullName }

Võiksime siin ära kasutada PowerShelli universaalsust ning ülaltoodud käsureast teha funktsiooni. Siis saaksime kaustade peal tehtava tegevuse (praegu utiliidi icacls käivitamine) asendada millegi muuga.

#Requires -Version 3
function ForDirs {
    param (
            [String]
        $Path = ".",
            [parameter(Mandatory="true")]
            [ScriptBlock]
        $Action
    )

    Get-ChildItem -Path $path -Recurse -Directory |
        Foreach-Object $action
}

Ja nüüd kasutame seda funktsiooni

ForDirs d:\folder {icacls $_.FullName}
ForDirs -a {$_}

$doit = {& 7z a -r ("{0}.zip" -f $_.Name) $_.FullPath; Remove-Item $_ }
ForDirs -Path d:\folder -Action $doit

Programmi käivitamine teise prioriteediga

Vahel on vaja, et saaks käivitada programmi madalama prioriteediga, kui muud programmid.  Vahel jälle on vaja käivitatud programmi prioriteeti tõsta.

Üks lihtne võimalus seda graafilises liideses, on Task Manageri abil.  Sellel on vaid üks puudus: nimelt tuleb programm kõigepealt käima panna.  Aga tavaliselt on selliseid asju vaja teha juba programmi käivitades.  Ja siis graafilisest liidesest kasu pole.

Lihtne on programmi prioriteeti muuta käsurealt, CMD.EXE seest käsuga START.  Näiteks saab programmi notepad.exe käivitada madalama prioriteediga:

start /belownormal notepad.exe

aga kui sama asja tahaks teha otseteega töölaualt, või käsurealt, siis peaks kirjutama hoopis:

cmd /c start /belownormal notepad.exe

Tuleb ainult meeles pidada, et  prioriteeti RealTime saab määrata ainult süsteemiülema õigustes isik.  Kui kasutajal süsteemiülema õigusi pole, siis saadakse tulemuseks prioriteet High.

Aga kui meil oleks vaja sama teha mitte käsitsi, vaid automaatselt?  Ja eesmärgiks oleks näiteks võtta hetkel liiga palju protsessori aega kulutav protsess ning seda korrale kutsuda?

CMD.EXE sai ilusti hakkama protsessi käivitamisel prioriteedi määramisega.  Ent juba töötava protsessi prioriteedi muutmisel jääb ta hätta.  Vaja läheb kolmanda partei utiliite, nagu näiteks SetPriority.  Või siis tuleb ära õppida WMI kasutamine otse käsurealt käsuga WMIC:

wmic process where name="notepad.exe" call setpriority "idle"

Windows Scripting Host saab asjaga paremini hakkama.  Tuleb küll kasutada WMI liidest, ent võimalus on olemas ning järgmine näidis on pärit TechNet Scripting Guide’ist:

Const ABOVE_NORMAL = 32768

strComputer = “.”
Set objWMIService = GetObject(“winmgmts:{impersonationLevel=impersonate}!\\” _
    & strComputer & “\root\cimv2“)
Set objStartup = objWMIService.Get(“Win32_ProcessStartup“)

Set objConfig = objStartup.SpawnInstance_
objConfig.PriorityClass = ABOVE_NORMAL

Set objProcess = GetObject(“winmgmts:root\cimv2:Win32_Process“)
objProcess.Create “Notepad.exe”, Null, objConfig, intProcessID

Ülaltoodud näide käivitab uue protsessi.  Järgnev näide muudab olemasoleva protsessi prioriteeti:

Const BELOW_NORMAL = 16384

strComputer = “.”
Set objWMIService = GetObject(“winmgmts:{impersonationLevel=impersonate}!\\” _
    & strComputer & “\root\cimv2“)

Set colProcesses = objWMIService.ExecQuery_
    (“Select * from Win32_Process Where Name = ‘Notepad.exe’”)

For Each objProcess in colProcesses
    objProcess.SetPriority(BELOW_NORMAL)
Next

Proovime nüüd sama asja PowerShellis.  Kõigepealt uue protsessi käivitamine:

#requires –Version 2.0
(Start-Process notepad.exe -PassThru).PriorityClass = "Idle"

Palju parem.  Aga nüüd näide sellest, kuidas muuta olemasoleva protsessi prioriteeti:

Get-Process notepad |
    ForEach-Object {$_.PriorityClass = "BelowNormal"}

Kui me tahame muuta prioriteeti protsessil, mida me ise ei ole käivitanud, siis on vaja omandada veel süsteemiülema õigused.  Aga seda sai kirjeldatud juba ühes varasemas artiklis.

Üks pisiasi veel.  Kui tahad teada kõiki prioriteediklasside nimesid, siis need annab järgmine näide:

[System.Enum]::GetValues([System.Diagnostics.ProcessPriorityClass])

Vanade (logi)failide töötlemine serveris

Serverites juhtub ikka, et erinevad rakendused peavad logisid.  Ja logidega kipub ikka see juhtuma, et need muudkui kogunevad ja keegi ei kustuta neid.  Ning siis äkki avastame, et serveris on kettaruum kuhugile ära kadunud.

Sedalaadi probleemide lahendamiseks on hea kasutada Windowsi käsurea utiliiti forfiles.  Windows Server 2003-s on see juba Windowsiga kaasas, varasemates versioonides seda veel ei olnud.  Windows NT4 ja  2000 Resource Kit sisaldavad seda utiliiti ka, ent tasuta kättesaadavate utiliitide hulgas seda kahjuks ei ole.

Tegelikult on forfiles mõeldud failide otsimiseks ning iga leitud faili peal sama käsu käivitamiseks.  Aga vanade logide puhul me just seda tahamegi.

Mõned näited ka.  Kui me tahame kustutada kõik vanemad kui 90 päeva failid kaustast d:\logs ja alamkaustadest, siis seda teeb järgnev käsk:

forfiles /p "d:\LOGS" /s /d -90 /c "cmd /c if @isdir==FALSE del /f /q @file"

Kui Sa aga tahad samast kaustast (ilma alamkaustadeta) ainult *.log failid kustutamise asemel kokku pakkida (ma kasutan selleks utiliiti 7-zip), siis seda teeb järgmine käsk:

forfiles /p "d:\LOGS" /m *.log /d -90 /c "cmd /c if @isdir==FALSE 7z a @fname.zip @file && del /f /q @file"

Selle käsu ainsaks probleemiks on see, et iga fail pakitakse samanimeliseks arhiiviks.  Kui on vaja failid kuu kaupa kokku pakkida, siis tuleks selleks eraldi skript kirjutada, mis arhiivi failinime oskaks välja arvutada.

Teeme samad näited läbi ka PowerShellis. Esimene näide:

Get-ChildItem -Path d:\logs -Recurse |
    Where-Object {-not $_.PSIsContainer} |
    Where-Object {((get-date) - $_.lastwritetime).days -gt 90} |
    Remove-Item

#Requires -Version 3
Get-ChildItem -Path d:\logs -Recurse -File |
    Where-Object {((get-date) - $_.lastwritetime).days -gt 90} |
    Remove-Item

Ja teine näide:

#Requires -Version 3
Get-ChildItem -Path d:\logs -Recurse -File |
    Where-Object {([datetime]::Now - $_.lastwritetime).days -gt 90} |
    Foreach-Object {
        & 7z a $("{0:yyyy.MM}.zip" -f [datetime]::Now.AddDays(-90)) $_.FullName
        Remove-Item $_
    }

Positiivne asja juures on nüüd see, et failid pakitakse kokku ühte arhiivi.  Arhiivi nimi sisaldab aastat ja kuud tänasest päevast 90 päeva tagasi.  Alternatiivina võiks failinime tuletada tänasest päevast 3 kuud tagasi:

"{0:yyyy.MM}.zip" -f (get-date).AddMonths(-3)