# ============================================================ # audit — Novatec.one PC Revizija v4.0 CSV # Pokretanje: irm https://sysinfo.novatec.one/audit | iex # novatec.one — IT podrska za moderne tvrtke # ============================================================ # Potrebne su samo dvije datoteke na serveru: # 1) audit (endpoint /audit) # 2) upload-revizija-v4.php # $CLIENT_VERSION = "4.0-csv" $EXPECTED_API_VERSION = "4.0-csv" $API_ENDPOINT = "https://sysinfo.novatec.one/upload-revizija-v4.php" $AUDIT_SOURCE_URL = "https://sysinfo.novatec.one/audit?release=4.0-csv" $REPORTS_DIR = "$env:TEMP\novatec_audit\izvjestaji" # ── DODATNO: slanje izravno na Novatec Dashboard ───────────────────────────── # Ne mijenja postojece slanje na sysinfo.novatec.one; portal je dodatno # odrediste. Ako je token prazan, ovaj dio se preskace bez ikakve promjene # dosadasnjeg ponasanja. # # Token generirajte na portalu: # sudo /opt/novatec-dashboard/current/scripts/configure-sysinfo-token.sh $PORTAL_ENDPOINT = "https://dash.novatec.one/api/sysinfo/ingest" $PORTAL_TOKEN = "" # <- ovdje zalijepite token s portala $PORTAL_TIMEOUT_SEC = 30 $WARRANTY_TIMEOUT_SEC = 12 # Dell TechDirect API - vjerodajnice se NE upisuju u skriptu. # Po potrebi ih postavi kao system environment varijable: # NOVATEC_DELL_CLIENT_ID # NOVATEC_DELL_CLIENT_SECRET $DELL_TOKEN_ENDPOINT = if ($env:NOVATEC_DELL_TOKEN_URL) { $env:NOVATEC_DELL_TOKEN_URL } else { "https://apigtwb2c.us.dell.com/auth/oauth/v2/token" } $DELL_WARRANTY_ENDPOINT = if ($env:NOVATEC_DELL_WARRANTY_URL) { $env:NOVATEC_DELL_WARRANTY_URL } else { "https://apigtwb2c.us.dell.com/PROD/sbil/eapi/v5/asset-entitlements" } # ── Konzolni ispis ─────────────────────────────────────────── function Write-Header($txt) { Write-Host "`n========================================" -ForegroundColor Cyan Write-Host " $txt" -ForegroundColor Cyan Write-Host "========================================`n" -ForegroundColor Cyan } function Write-OK($txt) { Write-Host "[OK] $txt" -ForegroundColor Green } function Write-ERR($txt) { Write-Host "[GRESKA] $txt" -ForegroundColor Red } function Write-INFO($txt) { Write-Host "[INFO] $txt" -ForegroundColor Yellow } # ── Sigurno zapisivanje i normalizacija ──────────────────────────────────────── function Write-JsonUtf8NoBom { [CmdletBinding()] param( [Parameter(Mandatory = $true)] $InputObject, [Parameter(Mandatory = $true)] [string] $Path, [ValidateRange(3, 100)] [int] $Depth = 20 ) $directory = Split-Path -Parent $Path if (-not (Test-Path -LiteralPath $directory)) { New-Item -ItemType Directory -Path $directory -Force -ErrorAction Stop | Out-Null } $tempPath = "$Path.tmp" try { $json = $InputObject | ConvertTo-Json -Depth $Depth $utf8NoBom = [System.Text.UTF8Encoding]::new($false) [System.IO.File]::WriteAllText($tempPath, $json, $utf8NoBom) Move-Item -LiteralPath $tempPath -Destination $Path -Force -ErrorAction Stop } catch { Remove-Item -LiteralPath $tempPath -Force -ErrorAction SilentlyContinue throw } } function Get-DirectorySizeTextWithTimeout { param( [Parameter(Mandatory = $true)][string] $Path, [ValidateRange(3, 120)][int] $TimeoutSeconds = 12 ) if (-not (Test-Path -LiteralPath $Path)) { return "N/A" } $job = $null try { $job = Start-Job -ArgumentList $Path -ScriptBlock { param($TargetPath) try { $sum = ( Get-ChildItem -LiteralPath $TargetPath -File -Recurse -Force -ErrorAction SilentlyContinue | Measure-Object -Property Length -Sum ).Sum if ($null -eq $sum) { return 0L } return [int64]$sum } catch { return $null } } $completed = Wait-Job -Job $job -Timeout $TimeoutSeconds if (-not $completed) { return "N/A (timeout ${TimeoutSeconds}s)" } $bytes = Receive-Job -Job $job -ErrorAction SilentlyContinue | Select-Object -Last 1 if ($null -eq $bytes) { return "N/A" } return "$([math]::Round(([double]$bytes / 1GB), 2)) GB" } catch { return "N/A" } finally { if ($job) { Stop-Job -Job $job -ErrorAction SilentlyContinue Remove-Job -Job $job -Force -ErrorAction SilentlyContinue } } } function ConvertTo-IsoDateSafe { param($Value) if ($null -eq $Value) { return "" } if ($Value -is [datetime]) { return $Value.ToString("yyyy-MM-dd") } $text = ([string]$Value).Trim() if ([string]::IsNullOrWhiteSpace($text)) { return "" } $formats = @( "yyyy-MM-dd", "yyyy-MM-ddTHH:mm:ss", "yyyy-MM-ddTHH:mm:ssZ", "yyyy-MM-ddTHH:mm:ss.fffZ", "MM/dd/yyyy", "dd.MM.yyyy", "yyyyMMdd" ) foreach ($format in $formats) { $parsed = [datetime]::MinValue if ([datetime]::TryParseExact( $text, $format, [System.Globalization.CultureInfo]::InvariantCulture, [System.Globalization.DateTimeStyles]::AllowWhiteSpaces, [ref]$parsed )) { return $parsed.ToString("yyyy-MM-dd") } } $fallback = [datetime]::MinValue if ([datetime]::TryParse( $text, [System.Globalization.CultureInfo]::InvariantCulture, [System.Globalization.DateTimeStyles]::AllowWhiteSpaces, [ref]$fallback )) { return $fallback.ToString("yyyy-MM-dd") } return "" } function New-WarrantyResult { param( [string] $Provider, [ValidateSet("Found", "NotFound", "NotConfigured", "Unsupported", "Skipped", "Error")] [string] $Status, [string] $SerialNumber, [string] $StartDate = "", [string] $EndDate = "", [string] $Product = "", [string] $Message = "" ) return [PSCustomObject][ordered]@{ Provider = $Provider Status = $Status SerialNumber = $SerialNumber StartDate = (ConvertTo-IsoDateSafe $StartDate) EndDate = (ConvertTo-IsoDateSafe $EndDate) Product = $Product Message = $Message CheckedAt = (Get-Date).ToUniversalTime().ToString("o") } } # ── Jamstva: provider adapteri, svaki poziv ima timeout i nikad ne ruši reviziju ── function Get-LenovoWarrantySafe { param([Parameter(Mandatory = $true)][string] $SerialNumber) try { $serial = $SerialNumber.Trim() $escapedSerial = [System.Uri]::EscapeDataString($serial) $headers = @{ "User-Agent" = "Novatec-System-Audit/3.1" "Accept" = "application/json" "Accept-Language" = "en-US,en;q=0.8" } $productResponse = Invoke-RestMethod ` -Uri "https://pcsupport.lenovo.com/us/en/api/v4/mse/getproducts?productId=$escapedSerial" ` -Method Get ` -Headers $headers ` -TimeoutSec $WARRANTY_TIMEOUT_SEC ` -ErrorAction Stop $productName = [string]$productResponse.Name $machineType = "" if ($productName -match "Type\s+([A-Z0-9]+)") { $machineType = $Matches[1] } $requestBody = [ordered]@{ serialNumber = $serial country = "us" language = "en" } if ($machineType) { $requestBody.machineType = $machineType } $warrantyResponse = Invoke-RestMethod ` -Uri "https://pcsupport.lenovo.com/us/en/api/v4/upsell/redport/getIbaseInfo" ` -Method Post ` -Headers $headers ` -ContentType "application/json" ` -Body ($requestBody | ConvertTo-Json -Depth 4) ` -TimeoutSec $WARRANTY_TIMEOUT_SEC ` -ErrorAction Stop $entries = @($warrantyResponse.Data.baseWarranties) $normalized = @( foreach ($entry in $entries) { $start = ConvertTo-IsoDateSafe $entry.startDate $end = ConvertTo-IsoDateSafe $entry.EndDate if ($start -or $end) { [PSCustomObject]@{ StartDate = $start; EndDate = $end } } } ) if ($normalized.Count -eq 0) { return New-WarrantyResult ` -Provider "Lenovo" ` -Status "NotFound" ` -SerialNumber $serial ` -Product ([string]$warrantyResponse.Data.machineInfo.product) ` -Message "Lenovo nije vratio valjan datum jamstva." } $startDate = ($normalized | Where-Object StartDate | Sort-Object StartDate | Select-Object -First 1).StartDate $endDate = ($normalized | Where-Object EndDate | Sort-Object EndDate -Descending | Select-Object -First 1).EndDate return New-WarrantyResult ` -Provider "Lenovo" ` -Status "Found" ` -SerialNumber $serial ` -StartDate $startDate ` -EndDate $endDate ` -Product ([string]$warrantyResponse.Data.machineInfo.product) ` -Message "Jamstvo dohvaćeno s Lenovo support servisa." } catch { return New-WarrantyResult ` -Provider "Lenovo" ` -Status "Error" ` -SerialNumber $SerialNumber ` -Message ("Lenovo warranty lookup nije uspio: " + $_.Exception.Message) } } function Get-DellWarrantySafe { param([Parameter(Mandatory = $true)][string] $SerialNumber) $clientId = $env:NOVATEC_DELL_CLIENT_ID $clientSecret = $env:NOVATEC_DELL_CLIENT_SECRET if ([string]::IsNullOrWhiteSpace($clientId) -or [string]::IsNullOrWhiteSpace($clientSecret)) { return New-WarrantyResult ` -Provider "Dell TechDirect" ` -Status "NotConfigured" ` -SerialNumber $SerialNumber ` -Message "Postavi NOVATEC_DELL_CLIENT_ID i NOVATEC_DELL_CLIENT_SECRET." } try { $tokenResponse = Invoke-RestMethod ` -Uri $DELL_TOKEN_ENDPOINT ` -Method Post ` -ContentType "application/x-www-form-urlencoded" ` -Body @{ client_id = $clientId client_secret = $clientSecret grant_type = "client_credentials" } ` -TimeoutSec $WARRANTY_TIMEOUT_SEC ` -ErrorAction Stop if ([string]::IsNullOrWhiteSpace([string]$tokenResponse.access_token)) { throw "Dell OAuth odgovor ne sadrži access_token." } $serviceTag = [System.Uri]::EscapeDataString($SerialNumber.Trim()) $uri = "$DELL_WARRANTY_ENDPOINT?servicetags=$serviceTag" $response = Invoke-RestMethod ` -Uri $uri ` -Method Get ` -Headers @{ Authorization = "Bearer $($tokenResponse.access_token)" Accept = "application/json" } ` -TimeoutSec $WARRANTY_TIMEOUT_SEC ` -ErrorAction Stop $assets = @() if ($response.assetEntitlementData) { $assets = @($response.assetEntitlementData) } elseif ($response.data) { $assets = @($response.data) } else { $assets = @($response) } $asset = $assets | Select-Object -First 1 if (-not $asset) { return New-WarrantyResult ` -Provider "Dell TechDirect" ` -Status "NotFound" ` -SerialNumber $SerialNumber ` -Message "Dell nije vratio uređaj za navedeni Service Tag." } $entitlements = @($asset.entitlements) $dates = @( foreach ($entitlement in $entitlements) { $start = ConvertTo-IsoDateSafe $entitlement.startDate $end = ConvertTo-IsoDateSafe $entitlement.endDate if ($start -or $end) { [PSCustomObject]@{ StartDate = $start; EndDate = $end } } } ) $startDate = ($dates | Where-Object StartDate | Sort-Object StartDate | Select-Object -First 1).StartDate if (-not $startDate) { $startDate = ConvertTo-IsoDateSafe $asset.shipDate } $endDate = ($dates | Where-Object EndDate | Sort-Object EndDate -Descending | Select-Object -First 1).EndDate $product = [string]$asset.productLineDescription if (-not $product) { $product = [string]$asset.productLobDescription } if (-not $startDate -and -not $endDate) { return New-WarrantyResult ` -Provider "Dell TechDirect" ` -Status "NotFound" ` -SerialNumber $SerialNumber ` -Product $product ` -Message "Dell je pronašao uređaj, ali nije vratio valjane datume jamstva." } return New-WarrantyResult ` -Provider "Dell TechDirect" ` -Status "Found" ` -SerialNumber $SerialNumber ` -StartDate $startDate ` -EndDate $endDate ` -Product $product ` -Message "Jamstvo dohvaćeno preko Dell TechDirect API-ja." } catch { return New-WarrantyResult ` -Provider "Dell TechDirect" ` -Status "Error" ` -SerialNumber $SerialNumber ` -Message ("Dell warranty lookup nije uspio: " + $_.Exception.Message) } } function Get-DeviceWarranty { param( [string] $Manufacturer, [string] $SerialNumber ) $make = ([string]$Manufacturer).Trim().ToUpperInvariant() $serial = ([string]$SerialNumber).Trim() if ([string]::IsNullOrWhiteSpace($serial) -or $serial -match "^(N/?A|NONE|UNKNOWN|DEFAULT STRING|TO BE FILLED)") { return New-WarrantyResult ` -Provider "None" ` -Status "Skipped" ` -SerialNumber $serial ` -Message "Serijski broj nije dostupan ili nije valjan." } if ($make -match "LENOVO") { return Get-LenovoWarrantySafe -SerialNumber $serial } if ($make -match "DELL") { return Get-DellWarrantySafe -SerialNumber $serial } if ($make -match "HEWLETT|(^|\s)HP($|\s)|HPE") { return New-WarrantyResult ` -Provider "HP" ` -Status "NotConfigured" ` -SerialNumber $serial ` -Message "HP nema pouzdan javni unattended lookup bez partnerskog/API pristupa; adapter je namjerno onemogućen umjesto screen-scrapinga." } return New-WarrantyResult ` -Provider $Manufacturer ` -Status "Unsupported" ` -SerialNumber $serial ` -Message "Za ovog proizvođača nije konfiguriran pouzdan warranty provider." } # ── ITFlow CSV projekcija ────────────────────────────────────────────────────── function Get-ITFlowAssetType { try { $chassisTypes = @((Get-CimInstance Win32_SystemEnclosure -ErrorAction Stop).ChassisTypes) if ($chassisTypes | Where-Object { $_ -in @(8, 9, 10, 11, 12, 14, 18, 21, 30, 31, 32) }) { return "Laptop" } if ($chassisTypes | Where-Object { $_ -in @(23, 28) }) { return "Server" } } catch { # Fallback je namjerno Desktop. } return "Desktop" } function Get-PrimaryNetworkData { param($NetworkItems) foreach ($adapter in @($NetworkItems)) { if (-not $adapter) { continue } $ipv4 = "" foreach ($candidate in (([string]$adapter.IP) -split ",")) { $ip = $candidate.Trim() if ($ip -match "^(?!127\.)(?!169\.254\.)\d{1,3}(\.\d{1,3}){3}$") { $ipv4 = $ip break } } $mac = ([string]$adapter.MAC).Trim() if ($ipv4 -or $mac) { return [PSCustomObject]@{ IP = $ipv4; MAC = $mac } } } return [PSCustomObject]@{ IP = ""; MAC = "" } } function New-ITFlowNotes { param( $Data, $Meta, $Warranty ) $parts = New-Object System.Collections.Generic.List[string] $cpuNames = @($Data.CPU | ForEach-Object { $_.Naziv } | Where-Object { $_ }) -join "; " if ($cpuNames) { $parts.Add("CPU: $cpuNames") } # RAM zapisi mogu biti hashtable ili PSCustomObject. Zbrajaj eksplicitno i # preskoci neispravne zapise kako ITFlow CSV nikada ne bi blokirao reviziju. [double]$ramTotal = 0 foreach ($ramModule in @($Data.RAM)) { if ($null -eq $ramModule) { continue } $rawSize = $null if ($ramModule -is [System.Collections.IDictionary]) { $rawSize = $ramModule['Velicina_GB'] } else { $sizeProperty = $ramModule.PSObject.Properties['Velicina_GB'] if ($sizeProperty) { $rawSize = $sizeProperty.Value } } if ($null -eq $rawSize -or [string]::IsNullOrWhiteSpace([string]$rawSize)) { continue } [double]$parsedSize = 0 $sizeText = ([string]$rawSize).Trim().Replace(',', '.') $parsed = [double]::TryParse( $sizeText, [System.Globalization.NumberStyles]::Float, [System.Globalization.CultureInfo]::InvariantCulture, [ref]$parsedSize ) if ($parsed -and $parsedSize -gt 0) { $ramTotal += $parsedSize } } if ($ramTotal -gt 0) { $parts.Add("RAM: $([math]::Round($ramTotal, 2)) GB") } $diskNotes = @( foreach ($disk in @($Data.Diskovi)) { if (-not $disk) { continue } $diskText = ([string]$disk.Model).Trim() if ($disk.Velicina_GB) { $diskText += " $($disk.Velicina_GB) GB" } if ($disk.Serijal -and $disk.Serijal -ne "N/A") { $diskText += " [$($disk.Serijal)]" } if ($diskText.Trim()) { $diskText.Trim() } } ) -join "; " if ($diskNotes) { $parts.Add("Diskovi: $diskNotes") } $gpuNames = @($Data.GPU | ForEach-Object { $_.Naziv } | Where-Object { $_ }) -join "; " if ($gpuNames) { $parts.Add("GPU: $gpuNames") } $monitorNotes = @( foreach ($monitor in @($Data.Monitori)) { if (-not $monitor) { continue } $monitorText = "$($monitor.Proizvodac) $($monitor.PartNumber)".Trim() if ($monitor.Serijal -and $monitor.Serijal -ne "N/A") { $monitorText += " [$($monitor.Serijal)]" } if ($monitorText.Trim()) { $monitorText.Trim() } } ) -join "; " if ($monitorNotes) { $parts.Add("Monitori: $monitorNotes") } $osText = ([string]$Data.OS.Naziv).Trim() if ($Data.OS.Build) { $osText += " Build $($Data.OS.Build)" } if ($osText.Trim()) { $parts.Add("OS: $($osText.Trim())") } $avNames = @($Data.WSC_Antivirus | ForEach-Object { $_.Naziv } | Where-Object { $_ }) -join "; " if ($avNames) { $parts.Add("Antivirus: $avNames") } $network = Get-PrimaryNetworkData -NetworkItems $Data.Mreza if ($network.IP -or $network.MAC) { $networkText = "Mreza:" if ($network.IP) { $networkText += " IP $($network.IP)" } if ($network.MAC) { $networkText += " MAC $($network.MAC)" } $parts.Add($networkText) } if ($Meta.Tvrtka) { $parts.Add("Tvrtka: $($Meta.Tvrtka)") } if ($Meta.Ime) { $parts.Add("Dodijeljeno: $($Meta.Ime)") } if ($Meta.Odjel) { $parts.Add("Fizicka lokacija: $($Meta.Odjel)") } if ($Meta.Napomena) { $parts.Add("Napomena: $($Meta.Napomena)") } if ($Warranty) { $warrantyText = "Jamstvo: $($Warranty.Status)" if ($Warranty.StartDate) { $warrantyText += " od $($Warranty.StartDate)" } if ($Warranty.EndDate) { $warrantyText += " do $($Warranty.EndDate)" } if ($Warranty.Provider) { $warrantyText += " ($($Warranty.Provider))" } if ($Warranty.Message -and $Warranty.Status -ne "Found") { $warrantyText += " - $($Warranty.Message)" } $parts.Add($warrantyText) } return $parts -join " | " } function New-ITFlowCsvRow { param( $Data, $Meta, $Warranty ) $serial = ([string]$Data.Racunalo.Serijal).Trim() if (-not $serial -or $serial -match "^(N/?A|NONE|UNKNOWN|DEFAULT STRING|TO BE FILLED)$") { $serial = ([string]$Data.MaticnaPloca.Serijal).Trim() } $assetType = Get-ITFlowAssetType $description = if ($assetType -eq "Server") { "Server" } else { "Radna stanica" } # ITFlow template nema zaseban stupac za datum isteka jamstva. # Purchase Date ostaje prazno jer datum jamstva nije datum kupnje; # podatak o jamstvu zapisuje se u Notes. return [PSCustomObject]([ordered]@{ Name = [string]$Data.Racunalo.Naziv Description = $description Type = $assetType Make = [string]$Data.Racunalo.Proizvodac Model = [string]$Data.Racunalo.Model Serial = $serial OS = [string]$Data.OS.Naziv "Purchase Date" = "" "Assigned To" = [string]$Meta.Ime Location = "Primary" "Physical Location" = [string]$Meta.Odjel Notes = (New-ITFlowNotes -Data $Data -Meta $Meta -Warranty $Warranty) }) } function ConvertTo-ITFlowCsvField { param($Value) if ($null -eq $Value) { return "" } $text = [string]$Value $escaped = $text.Replace('"', '""') if ($text -match '[,"\r\n]' -or $text -ne $text.Trim()) { return '"' + $escaped + '"' } return $escaped } function Write-ITFlowCsvUtf8NoBom { [CmdletBinding()] param( [Parameter(Mandatory = $true)] $Row, [Parameter(Mandatory = $true)] [string] $Path ) $directory = Split-Path -Parent $Path if (-not (Test-Path -LiteralPath $directory)) { New-Item -ItemType Directory -Path $directory -Force -ErrorAction Stop | Out-Null } # Header mora biti identican sluzbenom ITFlow Assets templateu. $header = 'Name,Description,Type,Make,Model,Serial,OS,"Purchase Date","Assigned To",Location,"Physical Location",Notes' $fields = @( (ConvertTo-ITFlowCsvField -Value $Row.Name), (ConvertTo-ITFlowCsvField -Value $Row.Description), (ConvertTo-ITFlowCsvField -Value $Row.Type), (ConvertTo-ITFlowCsvField -Value $Row.Make), (ConvertTo-ITFlowCsvField -Value $Row.Model), (ConvertTo-ITFlowCsvField -Value $Row.Serial), (ConvertTo-ITFlowCsvField -Value $Row.OS), (ConvertTo-ITFlowCsvField -Value $Row.'Purchase Date'), (ConvertTo-ITFlowCsvField -Value $Row.'Assigned To'), (ConvertTo-ITFlowCsvField -Value $Row.Location), (ConvertTo-ITFlowCsvField -Value $Row.'Physical Location'), (ConvertTo-ITFlowCsvField -Value $Row.Notes) ) $tempPath = "$Path.tmp" try { $utf8NoBom = [System.Text.UTF8Encoding]::new($false) [string[]]$lines = @($header, ($fields -join ',')) [System.IO.File]::WriteAllLines($tempPath, $lines, $utf8NoBom) Move-Item -LiteralPath $tempPath -Destination $Path -Force -ErrorAction Stop } catch { Remove-Item -LiteralPath $tempPath -Force -ErrorAction SilentlyContinue throw } } # ── Provjera admin prava ───────────────────────────────────── function Test-Admin { $id = [System.Security.Principal.WindowsIdentity]::GetCurrent() $p = [System.Security.Principal.WindowsPrincipal]$id return $p.IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator) } # ── Unos podataka tehnicara ─────────────────────────────────── function Get-TechnicianInput { Write-Header "UNOS PODATAKA DJELATNIKA" do { $ime = Read-Host " Ime i prezime djelatnika" Write-Host "" } while ([string]::IsNullOrWhiteSpace($ime)) do { $tvrtka = Read-Host " Ime tvrtke / obrta / krajnjeg korisnika" Write-Host "" } while ([string]::IsNullOrWhiteSpace($tvrtka)) do { $odjel = Read-Host " Odjel / Prostorija" Write-Host "" } while ([string]::IsNullOrWhiteSpace($odjel)) $napomena = Read-Host " Napomena (Enter za preskociti)" Write-Host "" return @{ Ime = $ime.Trim(); Tvrtka = $tvrtka.Trim(); Odjel = $odjel.Trim(); Napomena = $napomena.Trim() } } # ── Windows Product Key iz registra ────────────────────────── function Get-WindowsKey { try { $key = (Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion").DigitalProductId $keyOffset = 52 $isWin8 = ([math]::Floor($key[66] / 6)) -band 1 $key[66] = ($key[66] -band 0xF7) -bor (($isWin8 -band 2) * 4) $chars = "BCDFGHJKMPQRTVWXY2346789" $result = "" for ($i = 24; $i -ge 0; $i--) { $cur = 0 for ($j = 14; $j -ge 0; $j--) { $cur = $cur * 256 -bxor $key[$j + $keyOffset] $key[$j + $keyOffset] = [math]::Floor($cur / 24) $cur = $cur % 24 } $result = $chars[$cur] + $result if (($i % 5 -eq 0) -and ($i -ne 0)) { $result = "-" + $result } } return $result } catch { return "Nije dostupno" } } # ── Prikupljanje podataka ───────────────────────────────────── function Get-SystemData { Write-INFO "Prikupljam podatke o sustavu..." $data = @{} # OS Write-INFO " OS i aktivacija..." $os = Get-CimInstance Win32_OperatingSystem $lic = Get-CimInstance SoftwareLicensingProduct -Filter "PartialProductKey IS NOT NULL AND ApplicationID='55c92734-d682-4d71-983e-d6ec3f16059f'" -ErrorAction SilentlyContinue | Select-Object -First 1 $bootTime = $os.LastBootUpTime $uptime = (Get-Date) - $bootTime $uptimeStr = "$($uptime.Days)d $($uptime.Hours)h $($uptime.Minutes)m" $data.OS = @{ Naziv = $os.Caption Verzija = $os.Version Build = $os.BuildNumber Arhitektura = $os.OSArchitecture Instaliran = $os.InstallDate.ToString("dd.MM.yyyy") Zadnji_boot = $bootTime.ToString("dd.MM.yyyy HH:mm") Uptime = $uptimeStr Jezik = $os.MUILanguages -join ", " Licenca = if ($lic) { switch($lic.LicenseStatus){1{"Aktiviran"}2{"Grace period"}5{"Notifikacija"}default{"Nije aktiviran"}} } else { "Nepoznato" } ProductKey = Get-WindowsKey RAM_Ukupno = "$([math]::Round($os.TotalVisibleMemorySize/1MB, 2)) GB" RAM_Slobodno = "$([math]::Round($os.FreePhysicalMemory/1MB, 2)) GB" } # Racunalo & BIOS Write-INFO " Racunalo i BIOS..." $cs = Get-CimInstance Win32_ComputerSystem $bios = Get-CimInstance Win32_BIOS $data.Racunalo = @{ Naziv = $cs.Name Proizvodac = $cs.Manufacturer Model = $cs.Model Serijal = $bios.SerialNumber Tip = $cs.SystemType BIOS_Verzija = $bios.SMBIOSBIOSVersion BIOS_Datum = $bios.ReleaseDate.ToString("dd.MM.yyyy") BIOS_Vendor = $bios.Manufacturer Domena = $cs.Domain Korisnik = $cs.UserName RAM_Slotovi = $cs.TotalPhysicalMemory | ForEach-Object { "$([math]::Round($_/1GB,2)) GB ukupno" } } # Maticna ploca Write-INFO " Maticna ploca..." $mb = Get-CimInstance Win32_BaseBoard $data.MaticnaPloca = @{ Proizvodac = $mb.Manufacturer Model = $mb.Product Serijal = $mb.SerialNumber Verzija = $mb.Version } # CPU Write-INFO " CPU..." $cpus = Get-CimInstance Win32_Processor $data.CPU = $cpus | ForEach-Object { @{ Naziv = $_.Name.Trim() Proizvodac = $_.Manufacturer Jezgre = $_.NumberOfCores LogJezgre = $_.NumberOfLogicalProcessors MaxMHz = $_.MaxClockSpeed TrenutnoMHz = $_.CurrentClockSpeed Arhitektura = switch($_.Architecture){0{"x86"}9{"x64"}6{"IA64"}default{$_.Architecture}} Socket = $_.SocketDesignation L2_Cache_KB = $_.L2CacheSize L3_Cache_KB = $_.L3CacheSize } } # RAM moduli Write-INFO " RAM moduli..." $rams = Get-CimInstance Win32_PhysicalMemory $data.RAM = $rams | ForEach-Object { @{ Slot = $_.DeviceLocator Velicina_GB = [math]::Round($_.Capacity/1GB, 2) Tip = switch($_.MemoryType){20{"DDR"}21{"DDR2"}24{"DDR3"}26{"DDR4"}34{"DDR5"}default{"Nepoznato"}} Brzina_MHz = $_.Speed Proizvodac = $_.Manufacturer Part_Number = $_.PartNumber.Trim() Serijal = $_.SerialNumber Forma = switch($_.FormFactor){8{"DIMM"}12{"SO-DIMM"}default{"Nepoznato"}} } } # GPU Write-INFO " GPU..." $gpus = Get-CimInstance Win32_VideoController $data.GPU = $gpus | ForEach-Object { @{ Naziv = $_.Name RAM_MB = [math]::Round($_.AdapterRAM/1MB, 0) Rezolucija = "$($_.CurrentHorizontalResolution)x$($_.CurrentVerticalResolution)" Driver_Ver = $_.DriverVersion Driver_Datum = if($_.DriverDate){$_.DriverDate.ToString("dd.MM.yyyy")}else{"N/A"} Status = $_.Status } } # Diskovi Write-INFO " Diskovi..." $diskovi = Get-CimInstance Win32_DiskDrive $sveLogicke = Get-CimInstance Win32_LogicalDisk | Where-Object { $_.DriveType -eq 3 } $data.Diskovi = $diskovi | ForEach-Object { $disk = $_ $mojiLogicki = @() try { $assocPart = Get-CimAssociatedInstance -InputObject $disk -ResultClassName Win32_DiskPartition -ErrorAction Stop $mojiLogicki = $assocPart | ForEach-Object { Get-CimAssociatedInstance -InputObject $_ -ResultClassName Win32_LogicalDisk -ErrorAction SilentlyContinue } } catch { $mojiLogicki = $sveLogicke } @{ Model = $disk.Model Serijal = if($disk.SerialNumber){ $disk.SerialNumber.Trim() }else{ "N/A" } Velicina_GB = [math]::Round($disk.Size/1GB, 2) Tip = $disk.MediaType Interface = $disk.InterfaceType Firmware = $disk.FirmwareRevision Particije = @($mojiLogicki) | Where-Object { $_ } | ForEach-Object { @{ Slovo = $_.DeviceID Naziv = $_.VolumeName Ukupno_GB = [math]::Round($_.Size/1GB, 2) Slobodno_GB = [math]::Round($_.FreeSpace/1GB, 2) Zauzeto_pct = if($_.Size -gt 0){ [math]::Round(($_.Size - $_.FreeSpace)/$_.Size*100, 1) }else{ 0 } FileSystem = $_.FileSystem } } } } # Mreza Write-INFO " Mrezni adapteri..." $nic = Get-CimInstance Win32_NetworkAdapterConfiguration | Where-Object { $_.IPEnabled } $data.Mreza = $nic | ForEach-Object { @{ Opis = $_.Description MAC = $_.MACAddress IP = $_.IPAddress -join ", " Subnet = $_.IPSubnet -join ", " Gateway = $_.DefaultIPGateway -join ", " DNS = $_.DNSServerSearchOrder -join ", " DHCP = $_.DHCPEnabled } } # Monitori Write-INFO " Monitori..." $monitors = Get-CimInstance WmiMonitorID -Namespace root\wmi -ErrorAction SilentlyContinue $data.Monitori = $monitors | ForEach-Object { function Decode-MonitorString($arr) { if (-not $arr) { return "N/A" } $bytes = $arr | Where-Object { $_ -ne 0 } if (-not $bytes) { return "N/A" } return ([System.Text.Encoding]::ASCII.GetString([byte[]]$bytes)).Trim() } @{ Proizvodac = Decode-MonitorString $_.ManufacturerName PartNumber = Decode-MonitorString $_.ProductCodeID Serijal = Decode-MonitorString $_.SerialNumberID } } # Instalirani softver Write-INFO " Instalirani softver..." $paths = @( "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*", "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*", "HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*" ) $softver = $paths | ForEach-Object { Get-ItemProperty $_ -ErrorAction SilentlyContinue } | Where-Object { $_.DisplayName -and $_.DisplayName.Trim() -ne "" } | Select-Object DisplayName, DisplayVersion, Publisher, InstallDate | Sort-Object DisplayName -Unique $data.Softver = $softver | ForEach-Object { @{ Naziv = $_.DisplayName Verzija = $_.DisplayVersion Izdavac = $_.Publisher Instaliran = $_.InstallDate } } # Korisnici Write-INFO " Korisnicki racuni..." $data.Korisnici = Get-CimInstance Win32_UserAccount -Filter "LocalAccount=True" | ForEach-Object { @{ Ime = $_.Name Puni_naziv = $_.FullName Opis = $_.Description Aktivan = -not $_.Disabled Admin = $_.SID -like "*-500" -or (Get-CimInstance Win32_GroupUser | Where-Object { $_.PartComponent -like "*$($_.Name)*" -and $_.GroupComponent -like "*Administrators*" } | Select-Object -First 1) } } # Windows Update Write-INFO " Zadnja azuriranja..." $data.Azuriranja = Get-HotFix | Sort-Object InstalledOn -Descending | Select-Object -First 15 | ForEach-Object { @{ KB = $_.HotFixID; Opis = $_.Description; Datum = if($_.InstalledOn){$_.InstalledOn.ToString("dd.MM.yyyy")}else{"N/A"} } } # Startup programi Write-INFO " Startup programi..." $startupPaths = @( "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run", "HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run" ) $data.Startup = $startupPaths | ForEach-Object { $path = $_ try { $props = Get-ItemProperty $path -ErrorAction Stop if ($null -eq $props) { return } $props | Get-Member -MemberType NoteProperty -ErrorAction SilentlyContinue | Where-Object { $_.Name -notlike "PS*" } | ForEach-Object { @{ Naziv = $_.Name; Putanja = $props.$($_.Name); Lokacija = $path } } } catch {} } # Pisaci Write-INFO " Pisaci..." $data.Pisaci = Get-CimInstance Win32_Printer | ForEach-Object { $statusTxt = switch($_.PrinterStatus) { 1 {"Ostalo"} 2 {"Nepoznato"} 3 {"Spreman"} 4 {"Tiskanje"} 5 {"Zagrijavanje"} 6 {"Zaustavljeno"} 7 {"Offline"} default {"Status $($_.PrinterStatus)"} } @{ Naziv = $_.Name; Zadani = $_.Default; Status = $statusTxt; Lokalni = $_.Local; PortName = $_.PortName; IP = "" } } # Dohvati IP adrese TCP/IP portova zasebno pa spoji $tcpPorts = @{} Get-CimInstance Win32_TCPIPPrinterPort -ErrorAction SilentlyContinue | ForEach-Object { $tcpPorts[$_.Name] = $_.HostAddress } foreach ($p in $data.Pisaci) { if ($tcpPorts.ContainsKey($p.PortName)) { $p.IP = $tcpPorts[$p.PortName] } elseif ($p.PortName -like "USB*") { $p.IP = "USB" } elseif ($p.PortName -like "WSD*") { $p.IP = "WSD (mrezni)" } elseif ($p.PortName -like "SHRFAX*" -or $p.Naziv -match "Fax") { $p.IP = "Fax" } elseif ($p.PortName -match "FILE|PORTPROMPT" -or $p.Naziv -match "PDF|XPS|OneNote|Microsoft") { $p.IP = "Virtualni pisac" } else { $p.IP = $p.PortName } } # Mapirani diskovi Write-INFO " Mapirani diskovi..." $data.MapiraniDiskovi = Get-CimInstance Win32_LogicalDisk | Where-Object { $_.DriveType -eq 4 } | ForEach-Object { @{ Slovo = $_.DeviceID UNC_Put = $_.ProviderName Naziv = $_.VolumeName Velicina_GB = if($_.Size){ [math]::Round($_.Size/1GB, 2) }else{ 0 } Slobodno_GB = if($_.FreeSpace){ [math]::Round($_.FreeSpace/1GB, 2) }else{ 0 } } } # Trenutni korisnik Write-INFO " Trenutni korisnik..." $cuName = $env:USERNAME $cuDomain = $env:USERDOMAIN $cuProfile = $env:USERPROFILE $recentDir = "$cuProfile\AppData\Roaming\Microsoft\Windows\Recent" $recentFiles = if(Test-Path $recentDir) { Get-ChildItem $recentDir -File -ErrorAction SilentlyContinue | Sort-Object LastWriteTime -Descending | Select-Object -First 10 | ForEach-Object { @{ Naziv = $_.BaseName; Datum = $_.LastWriteTime.ToString("dd.MM.yyyy HH:mm") } } } else { @() } $oneDrivePath = (Get-ItemProperty "HKCU:\SOFTWARE\Microsoft\OneDrive" -ErrorAction SilentlyContinue).UserFolder $oneDriveEmail = (Get-ItemProperty "HKCU:\SOFTWARE\Microsoft\OneDrive\Accounts\Business1" -ErrorAction SilentlyContinue).UserEmail $outlookProfiles = (Get-ChildItem "HKCU:\SOFTWARE\Microsoft\Office\16.0\Outlook\Profiles" -ErrorAction SilentlyContinue | Select-Object -ExpandProperty PSChildName) -join ", " if (-not $outlookProfiles) { $outlookProfiles = (Get-ChildItem "HKCU:\SOFTWARE\Microsoft\Office\15.0\Outlook\Profiles" -ErrorAction SilentlyContinue | Select-Object -ExpandProperty PSChildName) -join ", " } $browserProgId = (Get-ItemProperty "HKCU:\SOFTWARE\Microsoft\Windows\Shell\Associations\UrlAssociations\https\UserChoice" -ErrorAction SilentlyContinue).ProgId $browserName = switch -Wildcard ($browserProgId) { "*Chrome*" { "Google Chrome" } "*Firefox*" { "Mozilla Firefox" } "*Edge*" { "Microsoft Edge" } "*Opera*" { "Opera" } "*Brave*" { "Brave" } default { if($browserProgId){ $browserProgId }else{ "Nepoznato" } } } # Rekurzivno mjerenje profila može trajati vrlo dugo na velikim profilima. # Izvršava se u zasebnom jobu i prekida nakon definiranog timeouta. $profileSize = Get-DirectorySizeTextWithTimeout -Path $cuProfile -TimeoutSeconds 12 $data.TrenutniKorisnik = @{ Ime = $cuName Domena = $cuDomain ProfilPutanja = $cuProfile VelicinaProfile = $profileSize AppData = $env:APPDATA Temp = $env:TEMP DefaultBrowser = $browserName OneDrivePutanja = if($oneDrivePath){ $oneDrivePath }else{ "Nije konfigurirano" } OneDriveEmail = if($oneDriveEmail){ $oneDriveEmail }else{ "N/A" } OutlookProfili = if($outlookProfiles){ $outlookProfiles }else{ "Nije konfigurirano" } NedavniDokumenti = $recentFiles } # Outlook — PST/OST datoteke i email računi po profilu Write-INFO " Outlook PST/OST datoteke i email racuni..." # --- Pretraga PST/OST — samo poznate zadane putanje (brzo) --- $pstOstFiles = @() # Dohvati sve korisnicke profile na sistemu $userProfiles = Get-CimInstance Win32_UserProfile | Where-Object { -not $_.Special -and (Test-Path $_.LocalPath) } | Select-Object -ExpandProperty LocalPath foreach ($uPath in $userProfiles) { $uName = Split-Path $uPath -Leaf # Poznate putanje za OST (IMAP/Exchange cache) $ostPaths = @( "$uPath\AppData\Local\Microsoft\Outlook" ) # Poznate putanje za PST (arhive, POP3) $pstPaths = @( "$uPath\Documents\Outlook Files", "$uPath\Moji dokumenti\Outlook Files", "$uPath\AppData\Local\Microsoft\Outlook", "$uPath\AppData\Roaming\Microsoft\Outlook" ) # Skeniraj OST putanje foreach ($p in $ostPaths) { if (-not (Test-Path $p)) { continue } Get-ChildItem -Path $p -Filter "*.ost" -File -ErrorAction SilentlyContinue | ForEach-Object { $pstOstFiles += @{ Naziv = $_.Name Tip = "OST" Putanja = $_.FullName Velicina_MB = [math]::Round($_.Length / 1MB, 1) Velicina_GB = [math]::Round($_.Length / 1GB, 2) Zadnja_izmjena = $_.LastWriteTime.ToString("dd.MM.yyyy HH:mm") Korisnik = $uName } } } # Skeniraj PST putanje foreach ($p in ($pstPaths | Select-Object -Unique)) { if (-not (Test-Path $p)) { continue } Get-ChildItem -Path $p -Filter "*.pst" -File -ErrorAction SilentlyContinue | ForEach-Object { # Izbjegni duplikate (ista datoteka u vise putanja) $fullPath = $_.FullName if (-not ($pstOstFiles | Where-Object { $_.Putanja -eq $fullPath })) { $pstOstFiles += @{ Naziv = $_.Name Tip = "PST" Putanja = $_.FullName Velicina_MB = [math]::Round($_.Length / 1MB, 1) Velicina_GB = [math]::Round($_.Length / 1GB, 2) Zadnja_izmjena = $_.LastWriteTime.ToString("dd.MM.yyyy HH:mm") Korisnik = $uName } } } } # Provjeri i registar za nestandardne putanje PST-a $regPaths = @( "HKCU:\SOFTWARE\Microsoft\Office\16.0\Outlook\Search\Catalog", "HKCU:\SOFTWARE\Microsoft\Office\15.0\Outlook\Search\Catalog", "HKCU:\SOFTWARE\Microsoft\Office\14.0\Outlook\Search\Catalog" ) foreach ($rp in $regPaths) { if (-not (Test-Path $rp)) { continue } try { $regVals = Get-ItemProperty $rp -ErrorAction SilentlyContinue $regVals.PSObject.Properties | Where-Object { $_.Name -like "*.pst" -or $_.Name -like "*.ost" } | ForEach-Object { $fPath = $_.Name if ((Test-Path $fPath) -and -not ($pstOstFiles | Where-Object { $_.Putanja -eq $fPath })) { $fi = Get-Item $fPath -ErrorAction SilentlyContinue $tip = [System.IO.Path]::GetExtension($fPath).TrimStart('.').ToUpper() if ($fi) { $pstOstFiles += @{ Naziv = $fi.Name Tip = $tip Putanja = $fi.FullName Velicina_MB = [math]::Round($fi.Length / 1MB, 1) Velicina_GB = [math]::Round($fi.Length / 1GB, 2) Zadnja_izmjena = $fi.LastWriteTime.ToString("dd.MM.yyyy HH:mm") Korisnik = $uName } } } } } catch {} } } $data.PstOstDatoteke = $pstOstFiles # --- Outlook email računi po profilu iz registra --- # Podržava Office 2010 (14.0), 2013 (15.0), 2016/2019/365 (16.0) $outlookVersions = @("16.0","15.0","14.0") $outlookProfileData = @() foreach ($ver in $outlookVersions) { $profRoot = "HKCU:\SOFTWARE\Microsoft\Office\$ver\Outlook\Profiles" if (-not (Test-Path $profRoot)) { continue } $profiles = Get-ChildItem $profRoot -ErrorAction SilentlyContinue foreach ($prof in $profiles) { $profName = $prof.PSChildName $emailAddresses = @() $accountNames = @() # Pretraži sve podkljuceve profila za email adrese try { $allSubKeys = Get-ChildItem -Path $prof.PSPath -Recurse -ErrorAction SilentlyContinue foreach ($sk in $allSubKeys) { try { $props = Get-ItemProperty -Path $sk.PSPath -ErrorAction SilentlyContinue # Email adresa može biti u više polja $emailFields = @('Email','Email Address','Account Name','SMTP Email Address', 'New Signature','ReplyToAddress') foreach ($ef in $emailFields) { $val = $props.$ef if ($val -and $val -match '@' -and $val -notmatch 'microsoft\.com$') { $emailAddresses += $val.Trim() } } # Binarno polje za email (čest format u Outlooku) $binFields = @('Email','SMTP Email Address','Account Name') foreach ($bf in $binFields) { $binVal = $props.$bf if ($binVal -is [byte[]]) { $decoded = [System.Text.Encoding]::Unicode.GetString($binVal).TrimEnd([char]0) if ($decoded -match '@' -and $decoded -notmatch 'microsoft\.com$') { $emailAddresses += $decoded.Trim() } } } # Display name / account name $dn = $props.'Display Name' if (-not $dn) { $dn = $props.'Account Name' } if ($dn -and $dn.Trim() -ne '' -and $dn -notmatch '^[{(]') { $accountNames += $dn.Trim() } } catch {} } } catch {} $emailAddresses = $emailAddresses | Where-Object { $_ } | Sort-Object -Unique $accountNames = $accountNames | Where-Object { $_ } | Sort-Object -Unique # Fallback: pokušaj naći email iz Office 365 / MAPI account sekcija if ($emailAddresses.Count -eq 0) { try { $acctPath = "$($prof.PSPath)\9375CFF0413111d3B88A00104B2A6676" if (Test-Path $acctPath) { Get-ChildItem $acctPath -ErrorAction SilentlyContinue | ForEach-Object { $p = Get-ItemProperty $_.PSPath -ErrorAction SilentlyContinue if ($p.'Account Name' -match '@') { $emailAddresses += $p.'Account Name'.Trim() } if ($p.'Email' -match '@') { $emailAddresses += $p.'Email'.Trim() } } } } catch {} } $outlookProfileData += @{ OfficeVerzija = $ver ProfilIme = $profName EmailAdrese = ($emailAddresses | Select-Object -Unique) -join "; " NaziviRacuna = ($accountNames | Select-Object -Unique) -join "; " } } } $data.OutlookProfili = $outlookProfileData # Licencirani i AV softver detekcija Write-INFO " Licencirani softver i AV detekcija..." $licKeywords = @( "ESET","Kaspersky","Symantec","Norton","McAfee","Bitdefender","Avast","AVG", "Malwarebytes","Sophos","Trend Micro","F-Secure","G Data","Panda","Webroot", "Comodo","Cylance","CrowdStrike","SentinelOne","Carbon Black","Windows Defender", "Avira","Emsisoft","Vipre","Bullguard","ZoneAlarm","Total AV","Dr.Web", "Microsoft Office","Microsoft 365","Office 365","Microsoft Word","Microsoft Excel", "Microsoft PowerPoint","Microsoft Outlook","Microsoft Teams","Microsoft Access", "Microsoft Publisher","Microsoft Visio","Microsoft Project", "Adobe Acrobat","Adobe Reader","Adobe Photoshop","Adobe Illustrator", "Adobe Premiere","Adobe After Effects","Adobe InDesign","Adobe Lightroom", "Adobe Creative Cloud","Adobe XD","Adobe Dreamweaver","Adobe Audition", "Adobe Bridge","Adobe Animate","Adobe Substance", "AutoCAD","Autodesk","SolidWorks","CATIA","Revit","Inventor","3ds Max","Maya", "SketchUp Pro","Rhino","ArchiCAD","Vectorworks", "SAP","Oracle","Pantheon","RRIF","Minimax","Vario","Navision", "Microsoft Dynamics","QuickBooks","Sage","DATEV","Lexware", "e-Racun","eConto","Moj-eRacun", "TeamViewer","AnyDesk","RustDesk","LogMeIn","Splashtop","Citrix", "Visual Studio","JetBrains","IntelliJ","PyCharm","WebStorm","Rider", "Eclipse","NetBeans","Sublime Text", "VMware","VirtualBox","Parallels", "Acronis","Veeam","Backup Exec","Macrium","AOMEI","EaseUS", "Slack","Zoom","Webex","GoToMeeting","Skype", "WinZip","WinRAR","PDF-XChange","Foxit","Nitro PDF", "Snagit","Camtasia","Bandicam", "LastPass","1Password","Bitwarden","KeePass", "Dropbox","Google Drive","Box" ) $avKeywords = @( "ESET","Kaspersky","Symantec","Norton","McAfee","Bitdefender","Avast","AVG", "Malwarebytes","Sophos","Trend Micro","F-Secure","G Data","Panda","Webroot", "Comodo","Cylance","CrowdStrike","SentinelOne","Carbon Black","Windows Defender", "Avira","Emsisoft","Vipre","Bullguard","ZoneAlarm","Total AV","Dr.Web" ) $data.LicenciraniSoftver = @($data.Softver) | Where-Object { $naziv = $_.Naziv $licKeywords | Where-Object { $naziv -like "*$_*" } } $data.AntivirusniProgrami = @($data.Softver) | Where-Object { $naziv = $_.Naziv $avKeywords | Where-Object { $naziv -like "*$_*" } } $wscAV = Get-CimInstance -Namespace "root\SecurityCenter2" -ClassName AntiVirusProduct -ErrorAction SilentlyContinue $data.WSC_Antivirus = $wscAV | ForEach-Object { $stateHex = "{0:X6}" -f $_.productState $realtime = if($stateHex[2] -eq '1'){"Ukljucen"}else{"Iskljucen"} $upToDate = if($stateHex[4] -eq '0'){"Azuran"}else{"Nije azuran"} @{ Naziv = $_.displayName; Realtime = $realtime; Azurnost = $upToDate } } return $data } # ── Generiranje HTML izvjestaja ─────────────────────────────── function New-HTMLReport($data, $meta, $hostname, $timestamp) { function Row($label, $value) { if (-not $value -or $value -eq "") { $value = "N/A" } return "$label$value" } $sectionCounter = 0 function Section($title, $icon) { $script:sectionCounter++ $num = $script:sectionCounter.ToString("D2") return "

$num$title

" } $css = @" "@ # Statistike $swCount = @($data.Softver).Count $diskCount = @($data.Diskovi).Count $ramTotal = ($data.RAM | ForEach-Object { $_.Velicina_GB } | Measure-Object -Sum).Sum # Diskovi HTML $diskoviHtml = "" foreach ($d in @($data.Diskovi)) { $diskoviHtml += "" $diskoviHtml += Row "Model" $d.Model $diskoviHtml += Row "Serijski broj" $d.Serijal $diskoviHtml += Row "Velicina" "$($d.Velicina_GB) GB" $diskoviHtml += Row "Tip medija" $d.Tip $diskoviHtml += Row "Interface" $d.Interface $diskoviHtml += Row "Firmware" $d.Firmware foreach ($p in @($d.Particije)) { $barPct = [math]::Min($p.Zauzeto_pct, 100) $barColor = if($p.Zauzeto_pct -gt 90){"#c0392b"}elseif($p.Zauzeto_pct -gt 70){"#e67e22"}else{"#27ae60"} $zauzetoGB = [math]::Round($p.Ukupno_GB - $p.Slobodno_GB, 2) $diskoviHtml += "" } $diskoviHtml += "
Particija $($p.Slovo) $($p.Naziv) ($($p.FileSystem))
Ukupno: $($p.Ukupno_GB) GB  |  Slobodno: $($p.Slobodno_GB) GB  |  Zauzeto: $zauzetoGB GB ($($p.Zauzeto_pct)%)
" } # RAM HTML $ramHtml = "" foreach ($r in @($data.RAM)) { $ramHtml += "" } $ramHtml += "
SlotVelicinaTipBrzinaProizvodacPart NumberSerijski broj
$($r.Slot)$($r.Velicina_GB) GB$($r.Tip) $($r.Forma)$($r.Brzina_MHz) MHz$($r.Proizvodac)$($r.Part_Number)$($r.Serijal)
" # CPU HTML $cpuHtml = "" foreach ($c in @($data.CPU)) { $cpuHtml += "" $cpuHtml += Row "Naziv" "$($c.Naziv)" $cpuHtml += Row "Jezgre / Logicke" "$($c.Jezgre) jezgri / $($c.LogJezgre) logickih" $cpuHtml += Row "Maks. frekvencija" "$($c.MaxMHz) MHz" $cpuHtml += Row "Socket" $c.Socket $cpuHtml += Row "L2 Cache" "$($c.L2_Cache_KB) KB" $cpuHtml += Row "L3 Cache" "$($c.L3_Cache_KB) KB" $cpuHtml += "
" } # GPU HTML $gpuHtml = "" foreach ($g in @($data.GPU)) { $gpuHtml += "" $gpuHtml += Row "Naziv" "$($g.Naziv)" $gpuHtml += Row "VRAM" "$($g.RAM_MB) MB" $gpuHtml += Row "Rezolucija" $g.Rezolucija $gpuHtml += Row "Driver verzija" $g.Driver_Ver $gpuHtml += Row "Driver datum" $g.Driver_Datum $gpuHtml += Row "Status" $g.Status $gpuHtml += "
" } # Mreza HTML $mrezaHtml = "" foreach ($n in @($data.Mreza)) { $mrezaHtml += "" $mrezaHtml += Row "Adapter" $n.Opis $mrezaHtml += Row "MAC adresa" "$($n.MAC)" $mrezaHtml += Row "IP adresa" $n.IP $mrezaHtml += Row "Subnet" $n.Subnet $mrezaHtml += Row "Gateway" $n.Gateway $mrezaHtml += Row "DNS" $n.DNS $mrezaHtml += Row "DHCP" $(if($n.DHCP){"Ukljucen"}else{"Iskljucen"}) $mrezaHtml += "
" } # Monitori HTML $monHtml = "" foreach ($m in @($data.Monitori)) { $monHtml += "" } $monHtml += "
ProizvodacPart NumberSerijski broj
$($m.Proizvodac)$($m.PartNumber)$($m.Serijal)
" # Antivirus HTML $avHtml = "" if (@($data.WSC_Antivirus).Count -gt 0) { $avHtml += "

Windows Security Center

" $avHtml += "" foreach ($av in @($data.WSC_Antivirus)) { $rtBadge = if($av.Realtime -eq "Ukljucen"){"Ukljucen"}else{"Iskljucen"} $azBadge = if($av.Azurnost -eq "Azuran"){"Azuran"}else{"Nije azuran"} $avHtml += "" } $avHtml += "
NazivReal-time zastitaAzurnost definicija
$($av.Naziv)$rtBadge$azBadge
" } if (@($data.AntivirusniProgrami).Count -gt 0) { $avHtml += "

Detektirani AV u popisu softvera

" foreach ($av in @($data.AntivirusniProgrami)) { $avHtml += "
$($av.Naziv)
" if ($av.Verzija) { $avHtml += "
v$($av.Verzija)
" } if ($av.Izdavac) { $avHtml += "
$($av.Izdavac)
" } $avHtml += "
" } $avHtml += "
" } if (-not $avHtml) { $avHtml = "

Nije detektiran nijedan antivirusni program.

" } # Licencirani softver HTML $licSwCount = @($data.LicenciraniSoftver).Count if ($licSwCount -gt 0) { $licSwHtml = "
" foreach ($s in @($data.LicenciraniSoftver)) { $licSwHtml += "
$($s.Naziv)
" if ($s.Verzija) { $licSwHtml += "
v$($s.Verzija)
" } if ($s.Izdavac) { $licSwHtml += "
$($s.Izdavac)
" } $licSwHtml += "
" } $licSwHtml += "
" } else { $licSwHtml = "

Nisu detektirani licencirani programi.

" } # Mapirani diskovi HTML if (@($data.MapiraniDiskovi).Count -gt 0) { $mapDiskHtml = "" foreach ($md in @($data.MapiraniDiskovi)) { $mapDiskHtml += "" } $mapDiskHtml += "
SlovoUNC putanjaNazivUkupnoSlobodno
$($md.Slovo)$($md.UNC_Put)$($md.Naziv)$($md.Velicina_GB) GB$($md.Slobodno_GB) GB
" } else { $mapDiskHtml = "

Nema mapiranih mreznih diskova.

" } # Trenutni korisnik HTML $cu = $data.TrenutniKorisnik $curUserHtml = "" $curUserHtml += Row "Korisnicko ime" "$($cu.Ime)" $curUserHtml += Row "Domena" $cu.Domena $curUserHtml += Row "Putanja profila" "$($cu.ProfilPutanja)" $curUserHtml += Row "Velicina profila" "$($cu.VelicinaProfile)" $curUserHtml += Row "AppData" "$($cu.AppData)" $curUserHtml += Row "Temp mapa" "$($cu.Temp)" $curUserHtml += Row "Zadani preglednik" $cu.DefaultBrowser $curUserHtml += Row "OneDrive putanja" "$($cu.OneDrivePutanja)" $curUserHtml += Row "OneDrive email" $cu.OneDriveEmail $curUserHtml += Row "Outlook profili" $cu.OutlookProfili $curUserHtml += "
" if (@($cu.NedavniDokumenti).Count -gt 0) { $curUserHtml += "

Zadnjih 10 nedavnih dokumenata

" $curUserHtml += "" foreach ($rd in @($cu.NedavniDokumenti)) { $curUserHtml += "" } $curUserHtml += "
NazivZadnja izmjena
$($rd.Naziv)$($rd.Datum)
" } # PST/OST HTML $pstOstHtml = "" $pstFiles = @($data.PstOstDatoteke) | Where-Object { $_.Tip -eq "PST" } $ostFiles = @($data.PstOstDatoteke) | Where-Object { $_.Tip -eq "OST" } if (@($data.PstOstDatoteke).Count -gt 0) { if ($pstFiles.Count -gt 0) { $pstOstHtml += "

PST arhive ($($pstFiles.Count))

" $pstOstHtml += "" foreach ($f in ($pstFiles | Sort-Object Velicina_MB -Descending)) { $size = if($f.Velicina_GB -ge 1){"$($f.Velicina_GB) GB"}else{"$($f.Velicina_MB) MB"} $pstOstHtml += "" } $pstOstHtml += "
NazivKorisnikVelicinaZadnja izmjenaPutanja
$($f.Naziv)$($f.Korisnik)$size$($f.Zadnja_izmjena)$($f.Putanja)
" } if ($ostFiles.Count -gt 0) { $pstOstHtml += "

OST datoteke ($($ostFiles.Count))

" $pstOstHtml += "" foreach ($f in ($ostFiles | Sort-Object Velicina_MB -Descending)) { $size = if($f.Velicina_GB -ge 1){"$($f.Velicina_GB) GB"}else{"$($f.Velicina_MB) MB"} $pstOstHtml += "" } $pstOstHtml += "
NazivKorisnikVelicinaZadnja izmjena
$($f.Naziv)$($f.Korisnik)$size$($f.Zadnja_izmjena)
" $pstOstHtml += "

Napomena: OST su lokalne kopije Exchange/M365 mailboxa. Putanja nije prikazana jer je uvijek unutar AppData profila.

" } } else { $pstOstHtml = "

Nisu pronadene PST/OST datoteke na ovom racunalu.

" } # Outlook profili HTML $outlookProfHtml = "" if (@($data.OutlookProfili).Count -gt 0) { $outlookProfHtml = "" foreach ($op in @($data.OutlookProfili)) { $emails = if($op.EmailAdrese){ $op.EmailAdrese }else{ "Nije pronadeno" } $names = if($op.NaziviRacuna){ $op.NaziviRacuna }else{ "N/A" } $outlookProfHtml += "" } $outlookProfHtml += "
ProfilEmail adreseNazivi racunaOffice verzija
$($op.ProfilIme)$emails$namesOffice $($op.OfficeVerzija)
" } else { $outlookProfHtml = "

Outlook profili nisu pronadeni (Outlook mozda nije instaliran ili nije konfiguriran).

" } # Softver HTML $swHtml = "
" foreach ($s in @($data.Softver)) { $swHtml += "
$($s.Naziv)
" if ($s.Verzija) { $swHtml += "
v$($s.Verzija)
" } if ($s.Izdavac) { $swHtml += "
$($s.Izdavac)
" } $swHtml += "
" } $swHtml += "
" # Azuriranja HTML $azHtml = "" foreach ($a in @($data.Azuriranja)) { $azHtml += "" } $azHtml += "
KBOpisDatum
$($a.KB)$($a.Opis)$($a.Datum)
" # Startup HTML $startHtml = "" foreach ($s in @($data.Startup)) { $startHtml += "" } $startHtml += "
NazivPutanjaLokacija
$($s.Naziv)$($s.Putanja)$($s.Lokacija)
" # Pisaci HTML $pisaciHtml = "" foreach ($p in @($data.Pisaci)) { $zadani = if($p.Zadani){"Da"}else{"Ne"} $ipDisplay = switch -Regex ($p.IP) { '^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$' { "$($p.IP)" } 'USB' { "USB" } 'Virtualni pisac' { "Virtualni" } 'WSD' { "WSD" } 'Fax' { "Fax" } 'Ispis u datoteku' { "Datoteka" } default { "$($p.IP)" } } $pisaciHtml += "" } $pisaciHtml += "
NazivIP adresaPortStatusZadaniLokalni
$($p.Naziv)$ipDisplay$($p.PortName)$($p.Status)$zadani$($p.Lokalni)
" # Korisnici HTML $korHtml = "" foreach ($k in @($data.Korisnici)) { $akt = if($k.Aktivan){"Da"}else{"Ne"} $korHtml += "" } $korHtml += "
KorisnikPuni nazivAktivan
$($k.Ime)$($k.Puni_naziv)$akt
" $licBadge = switch($data.OS.Licenca) { "Aktiviran" { "Aktiviran" } default { "$($data.OS.Licenca)" } } $html = @" PC Revizija - $hostname $css
PC Revizija · Interni alat

Izvjestaj: $hostname

Racunalo$($data.Racunalo.Naziv)
Djelatnik$($meta.Ime)
Tvrtka$($meta.Tvrtka)
Odjel / Prostorija$($meta.Odjel)
Datum revizije$(Get-Date -Format 'dd.MM.yyyy HH:mm')
OS$($data.OS.Naziv)
$(if($meta.Napomena){"
Napomena$($meta.Napomena)
"})
$swCount
Instaliranih programa
$diskCount
Diskova
$ramTotal GB
Ukupno RAM
$(@($data.CPU)[0].Jezgre)
CPU jezgri
$licBadge
Windows licenca
$(Section "Osnovno o racunalu" "🖥️") $(Row "Naziv racunala" $data.Racunalo.Naziv) $(Row "Proizvodac" $data.Racunalo.Proizvodac) $(Row "Model" "$($data.Racunalo.Model)") $(Row "Serijski broj" "$($data.Racunalo.Serijal)") $(Row "Tip sustava" $data.Racunalo.Tip) $(Row "Domena" $data.Racunalo.Domena) $(Row "Prijavljeni korisnik" $data.Racunalo.Korisnik)
$(Section "Operacijski sustav" "🪟") $(Row "Naziv" "$($data.OS.Naziv)") $(Row "Verzija / Build" "$($data.OS.Verzija) (Build $($data.OS.Build))") $(Row "Arhitektura" $data.OS.Arhitektura) $(Row "Datum instalacije" $data.OS.Instaliran) $(Row "Zadnje pokretanje" $data.OS.Zadnji_boot) $(Row "Uptime" "$($data.OS.Uptime)") $(Row "Aktivacija" $licBadge) $(Row "Windows Product Key" "
$($data.OS.ProductKey)
") $(Row "RAM (ukupno / slobodno)" "$($data.OS.RAM_Ukupno) / $($data.OS.RAM_Slobodno)")
$(Section "BIOS / UEFI" "⚙️") $(Row "Verzija" $data.Racunalo.BIOS_Verzija) $(Row "Datum" $data.Racunalo.BIOS_Datum) $(Row "Vendor" $data.Racunalo.BIOS_Vendor)
$(Section "Maticna ploca" "🔌") $(Row "Proizvodac" $data.MaticnaPloca.Proizvodac) $(Row "Model" "$($data.MaticnaPloca.Model)") $(Row "Serijski broj" "$($data.MaticnaPloca.Serijal)") $(Row "Verzija" $data.MaticnaPloca.Verzija)
$(Section "Procesor (CPU)" "🧠") $cpuHtml $(Section "Radna memorija (RAM)" "💾") $ramHtml $(Section "Graficka kartica (GPU)" "🎮") $gpuHtml $(Section "Diskovi i particije" "💿") $diskoviHtml $(Section "Monitori" "🖥️") $monHtml $(Section "Mrezni adapteri" "🌐") $mrezaHtml $(Section "Korisnici" "👤") $korHtml $(Section "Pisaci" "🖨️") $pisaciHtml $(Section "Startup programi" "🚀") $startHtml $(Section "Zadnjih 15 azuriranja" "🔄") $azHtml $(Section "Antivirusna zastita" "🛡️") $avHtml $(Section "Licencirani softver" "🔑") $licSwHtml $(Section "Mapirani mrezni diskovi" "🗂️") $mapDiskHtml $(Section "Outlook profili i email racuni" "📧") $outlookProfHtml $(Section "PST i OST datoteke" "📨") $pstOstHtml $(Section "Trenutni korisnik" "👤") $curUserHtml $(Section "Instalirani softver ($swCount programa)" "📦") $swHtml
"@ return $html } # ── Upload na API ───────────────────────────────────────────── function Send-ToPortal($jsonPath) { # Salje isti JSON koji vec ide na FTP, izravno portalu. # # Namjerno je izolirano od Send-Report: ako portal nije dostupan, kriv token # ili je izostavljen, revizija se nastavlja normalno i FTP upload nije # dotaknut. Ovaj poziv nikada ne smije oboriti skriptu. if ([string]::IsNullOrWhiteSpace($PORTAL_TOKEN)) { return $false } if (-not (Test-Path $jsonPath)) { Write-ERR "Portal: JSON nije pronadjen: $jsonPath" return $false } try { Write-INFO "Saljem na portal $PORTAL_ENDPOINT ..." $jsonBody = [System.IO.File]::ReadAllText($jsonPath, [System.Text.Encoding]::UTF8) Add-Type -AssemblyName System.Net.Http $portalClient = [System.Net.Http.HttpClient]::new() $portalClient.Timeout = [TimeSpan]::FromSeconds($PORTAL_TIMEOUT_SEC) # Token ide u zaglavlje, ne u URL: URL-ovi zavrsavaju u logovima proxyja. $portalClient.DefaultRequestHeaders.Add("X-Novatec-Token", $PORTAL_TOKEN) $content = [System.Net.Http.StringContent]::new( $jsonBody, [System.Text.Encoding]::UTF8, "application/json") try { $portalResponse = $portalClient.PostAsync($PORTAL_ENDPOINT, $content).GetAwaiter().GetResult() $portalBody = $portalResponse.Content.ReadAsStringAsync().GetAwaiter().GetResult() $portalCode = [int]$portalResponse.StatusCode if ($portalCode -ge 200 -and $portalCode -lt 300) { Write-OK "Portal: izvjestaj zaprimljen (HTTP $portalCode)." return $true } elseif ($portalCode -eq 401) { Write-ERR "Portal: token nije prihvacen. Provjerite \$PORTAL_TOKEN." } elseif ($portalCode -eq 429) { Write-ERR "Portal: previse zahtjeva, pokusajte kasnije." } else { Write-ERR "Portal: HTTP $portalCode - $portalBody" } return $false } finally { $content.Dispose() $portalClient.Dispose() } } catch { # Namjerno samo upozorenje: portal je dodatak, a ne uvjet za reviziju. Write-ERR "Portal: slanje neuspjesno - $_" return $false } } function Send-Report($meta, $htmlPath, $jsonPath, $itFlowCsvPath, $timestamp, $hostname, $lozinkaText) { Write-Header "UPLOAD NA SERVER" $requiredFiles = @( @{ Label = "HTML"; Path = $htmlPath }, @{ Label = "JSON"; Path = $jsonPath }, @{ Label = "ITFlow CSV"; Path = $itFlowCsvPath } ) $missingFiles = @($requiredFiles | Where-Object { -not (Test-Path -LiteralPath $_.Path -PathType Leaf) }) if ($missingFiles.Count -gt 0) { foreach ($missing in $missingFiles) { Write-ERR "$($missing.Label) datoteka nije pronadjena: $($missing.Path)" } Write-ERR "Upload nije pokrenut jer nedostaju obavezne datoteke." return $false } # Koristi HttpClient — pouzdaniji za upload datoteka od Invoke-WebRequest Add-Type -AssemblyName System.Net.Http $client = [System.Net.Http.HttpClient]::new() $client.Timeout = [TimeSpan]::FromSeconds(180) $mpContent = [System.Net.Http.MultipartFormDataContent]::new() function Add-StringField($name, $value) { $sc = [System.Net.Http.StringContent]::new($value, [System.Text.Encoding]::UTF8) $mpContent.Add($sc, $name) } function Add-FileField($fieldName, $filePath, $mimeType) { $bytes = [System.IO.File]::ReadAllBytes($filePath) $fc = [System.Net.Http.ByteArrayContent]::new($bytes) $fc.Headers.ContentType = [System.Net.Http.Headers.MediaTypeHeaderValue]::new($mimeType) $fileName = [System.IO.Path]::GetFileName($filePath) $mpContent.Add($fc, $fieldName, $fileName) } # Tekstualna polja Add-StringField "lozinka" $lozinkaText Add-StringField "client_version" $CLIENT_VERSION Add-StringField "ime_prezime" $meta.Ime Add-StringField "tvrtka" $meta.Tvrtka Add-StringField "odjel" $meta.Odjel Add-StringField "napomena" $meta.Napomena Add-StringField "racunalo" $hostname Add-StringField "datum_revizije" $timestamp Add-StringField "os_verzija" ((Get-CimInstance Win32_OperatingSystem).Caption) # HTML izvjestaj if (Test-Path $htmlPath) { Write-INFO "Dodajem HTML izvjestaj ($([math]::Round((Get-Item $htmlPath).Length/1KB, 1)) KB)..." Add-FileField "html_izvjestaj" $htmlPath "text/html" Write-OK "HTML dodan." } else { Write-ERR "HTML izvjestaj nije pronadjen: $htmlPath" } # Izvorni JSON podaci if (Test-Path $jsonPath) { Write-INFO "Dodajem JSON podatke ($([math]::Round((Get-Item $jsonPath).Length/1KB, 1)) KB)..." Add-FileField "json_podaci" $jsonPath "application/json" Write-OK "JSON dodan." } else { Write-ERR "JSON nije pronadjen: $jsonPath" } # ITFlow CSV prema Assets templateu if (Test-Path $itFlowCsvPath) { Write-INFO "Dodajem ITFlow CSV ($([math]::Round((Get-Item $itFlowCsvPath).Length/1KB, 1)) KB)..." Add-FileField "itflow_csv" $itFlowCsvPath "text/csv" Write-OK "ITFlow CSV dodan." } else { Write-ERR "ITFlow CSV nije pronadjen: $itFlowCsvPath" } $uploadSucceeded = $false try { Write-INFO "Saljem na $API_ENDPOINT ..." $response = $client.PostAsync($API_ENDPOINT, $mpContent).GetAwaiter().GetResult() $respBody = $response.Content.ReadAsStringAsync().GetAwaiter().GetResult() $code = [int]$response.StatusCode if ($code -ge 200 -and $code -lt 300) { $parsedResponse = $null try { $parsedResponse = $respBody | ConvertFrom-Json -ErrorAction Stop } catch {} if ($null -eq $parsedResponse -or [string]$parsedResponse.api_version -ne $EXPECTED_API_VERSION) { $returnedVersion = if ($parsedResponse) { [string]$parsedResponse.api_version } else { "nepoznata" } Write-ERR "Server je vratio HTTP $code, ali API verzija nije valjana: $returnedVersion" Write-ERR "Ocekivano: $EXPECTED_API_VERSION. Lokalni izvjestaji ostaju sacuvani." Write-ERR "Odgovor: $respBody" } else { Write-OK "Upload uspjesan! HTTP $code, API $($parsedResponse.api_version)" Write-INFO "Odgovor: $respBody" $uploadSucceeded = $true } } else { Write-ERR "Server vratio: HTTP $code" Write-ERR "Odgovor: $respBody" } } catch { Write-ERR "Upload neuspjesan: $_" } finally { $mpContent.Dispose() $client.Dispose() } return $uploadSucceeded } # ════════════════════════════════════════════════════════════ # MAIN # ════════════════════════════════════════════════════════════ Clear-Host Write-Host "" Write-Host " NOVATEC.ONE - PC REVIZIJA v4.0 CSV" -ForegroundColor Cyan Write-Host " ================================" -ForegroundColor Cyan Write-Host " https://sysinfo.novatec.one" -ForegroundColor DarkCyan Write-Host "" if (-not (Test-Admin)) { Write-INFO "Pokretanje s administratorskim pravima..." # Ponovno dohvati tocno v4 izdanje bez proxy/browser cachea i pokreni ga kao admin. $scriptContent = (Invoke-WebRequest ` -Uri $AUDIT_SOURCE_URL ` -UseBasicParsing ` -Headers @{ "Cache-Control" = "no-cache"; "Pragma" = "no-cache" } ` -TimeoutSec 30 ).Content $tmpScript = "$env:TEMP ovatec_audit_v4_run.ps1" $scriptContent | Out-File -FilePath $tmpScript -Encoding UTF8 -Force Start-Process powershell.exe -ArgumentList "-NoProfile -ExecutionPolicy Bypass -File `"$tmpScript`"" -Verb RunAs exit 0 } # ── Provjera lozinke ───────────────────────────────────────── Write-Header "AUTENTIFIKACIJA" $maxPokusaja = 3 $pokusaj = 0 $autentificiran = $false while ($pokusaj -lt $maxPokusaja) { $pokusaj++ $lozinka = Read-Host " Lozinka" -AsSecureString $bstr = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($lozinka) try { $lozinkaText = [System.Runtime.InteropServices.Marshal]::PtrToStringBSTR($bstr) } finally { [System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($bstr) } # Provjeri lozinku na serveru try { $provjera = Invoke-WebRequest ` -Uri "$API_ENDPOINT" ` -Method POST ` -Body @{ action = "provjera_lozinke"; lozinka = $lozinkaText; client_version = $CLIENT_VERSION } ` -UseBasicParsing ` -TimeoutSec 15 $odgovor = $provjera.Content | ConvertFrom-Json if ($odgovor.status -eq "ok") { $serverVersion = [string]$odgovor.api_version if ($serverVersion -ne $EXPECTED_API_VERSION) { Write-ERR "Nepodudarne verzije: audit=$CLIENT_VERSION, server=$serverVersion." Write-ERR "Na server postavi upload-revizija-v4.php i ponovno dohvati /audit?release=4.0-csv." break } Write-OK "Autentifikacija uspjesna. API verzija: $serverVersion" $autentificiran = $true break } else { $preostalo = $maxPokusaja - $pokusaj if ($preostalo -gt 0) { Write-ERR "Pogresna lozinka. Preostalo pokusaja: $preostalo" } } } catch { $statusCode = $null $serverBody = "" try { if ($_.Exception.Response) { $statusCode = [int]$_.Exception.Response.StatusCode $responseStream = $_.Exception.Response.GetResponseStream() if ($responseStream) { $reader = New-Object System.IO.StreamReader($responseStream) try { $serverBody = $reader.ReadToEnd() } finally { $reader.Dispose() } } } } catch { # Dijagnostika ne smije prekinuti glavni error handler. } if ($statusCode) { Write-ERR "Server je vratio HTTP $statusCode." } else { Write-ERR "Nije moguce kontaktirati server: $($_.Exception.Message)" } if (-not [string]::IsNullOrWhiteSpace($serverBody)) { Write-ERR "Odgovor servera: $serverBody" } else { Write-ERR "Server nije vratio detalje. Provjeri PHP error log i je li upload-revizija.php na tocnoj putanji." } break } } if (-not $autentificiran) { Write-ERR "Autentifikacija neuspjesna. Skripta se zaustavlja." Start-Sleep -Seconds 3 exit 1 } $meta = Get-TechnicianInput $timestamp = Get-Date -Format "yyyyMMdd_HHmmss" $hostname = $env:COMPUTERNAME $outDir = "$REPORTS_DIR\${hostname}_${timestamp}" New-Item -ItemType Directory -Path $outDir -Force | Out-Null Write-Header "PRIKUPLJANJE PODATAKA" $data = Get-SystemData Write-INFO "Dohvacam podatke o jamstvu (maks. $WARRANTY_TIMEOUT_SEC s po pozivu)..." $warranty = Get-DeviceWarranty ` -Manufacturer ([string]$data.Racunalo.Proizvodac) ` -SerialNumber ([string]$data.Racunalo.Serijal) $data.Jamstvo = $warranty switch ($warranty.Status) { "Found" { Write-OK "Jamstvo: $($warranty.Provider), $($warranty.StartDate) do $($warranty.EndDate)" } "Error" { Write-ERR "Jamstvo nije dohvaćeno: $($warranty.Message)" } default { Write-INFO "Jamstvo: $($warranty.Status) - $($warranty.Message)" } } Write-INFO "Generiram HTML izvjestaj..." $html = New-HTMLReport $data $meta $hostname $timestamp $reportPath = "$outDir\revizija_${hostname}_${timestamp}.html" $html | Out-File -FilePath $reportPath -Encoding UTF8 Write-OK "HTML izvjestaj spremljen: $reportPath" # Spremi puni JSON sa svim prikupljenim podacima Write-INFO "Generiram puni JSON..." $fullJson = @{ meta = @{ ime_prezime = $meta.Ime tvrtka = $meta.Tvrtka odjel = $meta.Odjel napomena = $meta.Napomena racunalo = $hostname datum_revizije = $timestamp os_verzija = $data.OS.Naziv } os = $data.OS racunalo = $data.Racunalo maticna_ploca = $data.MaticnaPloca cpu = $data.CPU ram = $data.RAM gpu = $data.GPU diskovi = $data.Diskovi mapirani_diskovi = $data.MapiraniDiskovi monitori = $data.Monitori mreza = $data.Mreza korisnici = $data.Korisnici trenutni_korisnik = $data.TrenutniKorisnik pisaci = $data.Pisaci startup = $data.Startup azuriranja = $data.Azuriranja softver = $data.Softver licencirani_softver = $data.LicenciraniSoftver antivirusni = $data.AntivirusniProgrami wsc_antivirus = $data.WSC_Antivirus pst_ost = $data.PstOstDatoteke outlook_profili = $data.OutlookProfili jamstvo = $data.Jamstvo } $jsonPath = "$outDir\podaci_${hostname}_${timestamp}.json" Write-JsonUtf8NoBom -InputObject $fullJson -Path $jsonPath -Depth 20 Write-OK "JSON spremljen: $jsonPath ($([math]::Round((Get-Item $jsonPath).Length/1KB,1)) KB)" # Dodatni ITFlow CSV: isto osnovno ime kao izvorni JSON, uz prefiks "IT-Flow". # Primjer: podaci_PC_20260716_120000.json -> IT-Flowpodaci_PC_20260716_120000.csv Write-INFO "Generiram ITFlow CSV..." $itFlowRow = New-ITFlowCsvRow -Data $data -Meta $meta -Warranty $warranty $itFlowFileName = "IT-Flow" + [System.IO.Path]::GetFileNameWithoutExtension($jsonPath) + ".csv" $itFlowCsvPath = Join-Path $outDir $itFlowFileName Write-ITFlowCsvUtf8NoBom -Row $itFlowRow -Path $itFlowCsvPath Write-OK "ITFlow CSV spremljen: $itFlowCsvPath ($([math]::Round((Get-Item $itFlowCsvPath).Length/1KB,1)) KB)" $uploadSucceeded = Send-Report $meta $reportPath $jsonPath $itFlowCsvPath $timestamp $hostname $lozinkaText # Dodatno slanje portalu. Rezultat se biljezi, ali ne utjece na $uploadSucceeded # niti na ostatak revizije - portal je drugo odrediste, ne zamjena za FTP. $portalSucceeded = Send-ToPortal $jsonPath Write-Header "ZAVRSENO" # Kopiraj HTML u temp prije brisanja (da browser moze otvoriti) $tempHtml = "$env:TEMP\novatec_revizija_${hostname}_${timestamp}.html" Copy-Item -Path $reportPath -Destination $tempHtml -Force Write-INFO "Otvaram izvjestaj u browseru..." Start-Process $tempHtml Start-Sleep -Seconds 1 if ($uploadSucceeded) { Write-INFO "Brisem lokalnu kopiju nakon uspjesnog uploada..." try { Remove-Item -Path $outDir -Recurse -Force -ErrorAction Stop Write-OK "Lokalna kopija obrisana." } catch { Write-ERR "Nije moguce obrisati lokalnu kopiju: $_" } } else { Write-ERR "Upload nije potvrden. Lokalni izvjestaji NISU obrisani." Write-INFO "Sacuvano za debug/retry: $outDir" } # Obriši temp HTML i ps1 skriptu nakon 30 sekundi u pozadini $tmpScript = "$env:TEMP\novatec_audit_v4_run.ps1" Start-Job -ScriptBlock { param($html, $ps1) Start-Sleep -Seconds 30 Remove-Item -Path $html -Force -ErrorAction SilentlyContinue Remove-Item -Path $ps1 -Force -ErrorAction SilentlyContinue } -ArgumentList $tempHtml, $tmpScript | Out-Null Write-Host "" Start-Sleep -Seconds 2