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.

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

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)