microsoft / DSCEA

DSC Environment Analyzer (DSCEA) is a simple implementation of PowerShell Desired State Configuration that uses the declarative nature of DSC to scan systems in an environment against a defined reference MOF file and generate compliance reports as to whether systems match the desired configuration.
https://microsoft.github.io/DSCEA
Other
197 stars 41 forks source link

Dev #14

Closed krjhitch closed 7 years ago

krjhitch commented 7 years ago

This one might require some rigorous testing

msftclas commented 7 years ago

@krjhitch, Thanks for your contribution as a Microsoft full-time employee or intern. You do not need to sign a CLA. Thanks, Microsoft Pull Request Bot

rkyttle commented 7 years ago

Ran some initial tests and having some issues.

First I ran the following, which created a valid working XML successfully: Start-DscEaScan -ComputerName localhost, dsctest40, junkpc -MofFile 'C:\temp\localhost.mof'

But I got errors when I tried to run Get-DscEaReport -Overall Copy-Item : Could not find a part of the path 'C:\ProgramData\DSCEA\logo.png'

Looking at the Get-DscEaReport function, I see that the error relates to the following code, which when run on its own also throws errors:

    if(-not (Test-Path 'C:\ProgramData\DSCEA\logo.png')) {
        $env:PSModulePath -split ';' | ForEach-Object {
            if(Test-Path (Join-Path $_ 'DSCEA\resources\logo.png')) {
                Copy-Item (Join-Path $_ 'DSCEA\resources\logo.png') 'C:\ProgramData\DSCEA\logo.png' -Force
            }
        }
    }

It seems the current code is not checking to make sure there is already a DSCEA folder inside of ProgramData

I was able to get this working with the following edits

    if(-not (Test-Path C:\ProgramData\DSCEA)) {
    New-Item C:\ProgramData\DSCEA -type directory    
    }

    if(-not (Test-Path 'C:\ProgramData\DSCEA\logo.png')) {
        $env:PSModulePath -split ';' | ForEach-Object {
            if(Test-Path (Join-Path $_ 'DSCEA\resources\logo.png')) {
                Copy-Item (Join-Path $_ 'DSCEA\resources\logo.png') 'C:\ProgramData\DSCEA\' -Force
            }
        }
    }

It also looks like these two versions of the HTML report are writing to the ProgramData directory, when they should be writing to the relative path instead, which is how overall and detailed are correctly behaving:

    if($ItemName){
        $results | ForEach-Object {
            $_.Compliance | ForEach-Object {
                $_.ResourcesInDesiredState | ForEach-Object {$_ | Select-Object @{Name="Computer";Expression={$_.PSComputerName}}, ResourceName, InstanceName, InDesiredState}
                $_.ResourcesNotInDesiredState | ForEach-Object {$_ | Select-Object @{Name="Computer";Expression={$_.PSComputerName}}, ResourceName, InstanceName, InDesiredState}
            }
        } | Where-object {$_.InstanceName -ieq $ItemName} | 
        ConvertTo-HTML -Head $webstyle -body "<img src='C:\ProgramData\DSCEA\logo.png'/><br>","<titlesection>DSC Configuration Report</titlesection><br>","<datesection>Report last run on",$date,"</datesection><p>" | 
        Out-File $OutPath\ItemComplianceReport-$ItemName.html
        Write-Host "Report $OutPath\ItemComplianceReport-$ItemName.html generated"
    }
    if($ComputerName){
        $results | where-object {$_.Computer -ieq $ComputerName} | ForEach-Object {
            $_.Compliance | ForEach-Object {
                $_.ResourcesNotInDesiredState | Select-Object @{Name="Computer";Expression={$_.PSComputerName}}, ResourceName, InstanceName, InDesiredState
                $_.ResourcesInDesiredState | Select-Object @{Name="Computer";Expression={$_.PSComputerName}}, ResourceName, InstanceName, InDesiredState
            }
        } | ConvertTo-HTML -Head $webstyle -body "<img src='C:\ProgramData\DSCEA\logo.png'/><br>","<titlesection>DSC Configuration Report</titlesection><br>","<datesection>Report last run on",$date,"</datesection><p>" | Out-File $OutPath\ComputerComplianceReport-$ComputerName.html 
        Write-Host "Report $OutPath\ComputerComplianceReport-$ComputerName.html generated"
    }
krjhitch commented 7 years ago

Good calls on that

Added

if(-not (Test-Path C:\ProgramData\DSCEA)) {
        New-Item C:\ProgramData\DSCEA -type directory    
}

And made OutPath work correctly

[String]$OutPath = '.'
Out-File (Join-Path -Path $OutPath -ChildPath 'OverallComplianceReport.html')
Get-ItemProperty (Join-Path -Path $OutPath -ChildPath 'OverallComplianceReport.html')
Out-File (Join-Path -Path $OutPath -ChildPath 'DetailedComplianceReport.html')
Get-ItemProperty (Join-Path -Path $OutPath -ChildPath 'DetailedComplianceReport.html')
Out-File (Join-Path -Path $OutPath -ChildPath "ItemComplianceReport-$ItemName.html")
Get-ItemProperty (Join-Path -Path $OutPath -ChildPath "ItemComplianceReport-$ItemName.html")
Out-File (Join-Path -Path $OutPath -ChildPath "ComputerComplianceReport-$ComputerName.html")
Get-ItemProperty (Join-Path -Path $OutPath -ChildPath "ComputerComplianceReport-$ComputerName.html")

I love the PowerShell styling! Woo

rkyttle commented 7 years ago

Good deal, and I agree I love the styling as well. Was just taking a look at function Convert-DSCEAresultsToCSV and I think we need to add the exceptions file if statement logic to it

krjhitch commented 7 years ago

I thought the POC for tactical only extended as far as the CSV? For the exceptions we'd need to find the file on the filesystem, show people how it's expected to be formatted, that whole mess

If we still want the exceptions tactically, I wonder if it makes sense to have the user pass them in as a hashtable, sort of like

Get-WinEvent -FilterHashTable @{Log='Application';Date=(Get-Date)}
rkyttle commented 7 years ago

So I had alreadfy created a solution for the tactical version, here is an example command

Get-DscEaPowerBiReport -ExceptionsFile 'C:\Users\ralph\Documents\DSCEA-exceptions-clean.ps1'

Here is an example of an exceptions file:

$OutPath = "$env:ProgramFiles\DSCEA\Output"

#Remove Global exceptions from results
$DataMinusGlobalExceptions = Import-Csv $OutPath\DAY.CSV |
Where-Object {($_.ResourceName -ne "Registry" -or $_.InstanceName -ne "Numberofpreviouslogonstocache") -and
($_.ResourceName -ne "Service" -or $_.InstanceName -ne "MicrosoftAntimalwareService") #-and
#($_.ResourceName -ne "WindowsFeature" -or $_.InstanceName -ne "XPSViewer")
}

#Remove individual system level exceptions
$DataMinusExceptions = $DataMinusGlobalExceptions |
Where-Object {($_.PSComputerName -ne "dsctest31" -or $_.ResourceName -ne "WindowsFeature" -or $_.InstanceName -ne "Bitlocker") -and
($_.PSComputerName -ne "dsctest31" -or $_.ResourceName -ne "WindowsFeature" -or $_.InstanceName -ne "EnhancedStorage") -and
($_.PSComputerName -ne "dsctest32" -or $_.ResourceName -ne "WindowsFeature" -or $_.InstanceName -ne "DHCPServer")
}

$DataMinusExceptions | Export-Csv -Path $OutPath\DAY.CSV -NoTypeInformation
krjhitch commented 7 years ago

I don't have the full thought process on it yet, but I feel like we should be able to read in an exceptions hashtable like

@{
    ComputersToExempt = @('server1','server2')
    ItemsToExempt = @('AntiMalware')
    ComputerItemPairsToExempt = @{
        'server3' = @('DNSServer')
        'server4' = @('DNSServer','NumLogons')
    }
}

Then we can use Get-PowerShellDataFile or whatever it's called, and do a few -notin $Data.ComputersToExempt type commands

In any case what I'm really wondering is if we can commit dev to master without it, and then raise it on the issues page, or if we should fix it before moving to master (since it'll break functionality)

rkyttle commented 7 years ago

Gotcha. I'm fine holding it off for now, and for making improvements to it as part of a separate issue.

As far as everything else goes, all looks good except for the write-warning on a scantimeout if the scan fails to complete because a scantimeout is set.

I was able to get my previous Pester integration test working on this latest update, and the only thing it flags is the following:

Start-DscEaScan -ComputerName localhost -MofFile 'C:\temp\localhost.mof' -ScanTimeout 1

When you run this, it does properly timeout, but the warning doesn't work properly right now. Instead of displaying a proper warning, it currently displays the following:

Write-Warning : A positional parameter cannot be found that accepts argument '1'.
At C:\Program Files\WindowsPowerShell\Modules\DSCEA\Functions\Start-DscEaScan.ps1:204 char:13
+             Write-Warning "The DSCEA scan was unable to complete beca ...
+             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidArgument: (:) [Write-Warning], ParameterBindingException
    + FullyQualifiedErrorId : PositionalParameterNotFound,Microsoft.PowerShell.Commands.WriteWarningCommand