Fix DFSR 4012 event and MaxOfflineTimeInDays

What a nice way to start 2017, I’ve got group policies not applying to computers because the DFS replication of the Sysvol is stopped and the delay of 60 days to resume the replication is over.

sysvol-dfsr-4012

Happy new year 🙂

NB1: don’t do what is recommended in the above message and when it says “To resume replication of this folder, use the DFS Management snap-in to remove this server from the replication group, and then add it back to the group.”.
This does not apply to a SYSVOL share on a domain controller, right?

NB2: If you wait for error 4012 and ignore warnings, it’s too late. Why did the domain admin wait for more than 60 days to resume replication? Nobody is reading the alerts and saw the warnings (events Id 2213) and/or worse the remediation script is also broken and there isn’t an alert for that 😦

NB3: notice that the above message tells me how long it is disconneted.
This server has been disconnected from other partners for ? days: 71 in my case.

I did the following to get back the replication of the SYSVOL working:

$i = gwmi -namespace root\microsoftdfs -query 'Select * FROM DfsrMachineConfig'
# increase the MaxOfflineTimeInDays to more than just a day
# 71+4=75
$i.MaxOfflineTimeInDays =[uint32]75
$i.Put()
Restart-Service -Name DFSR -Verbose

How to export all URLs of Firefox tabs at once

I’ve seen the following script proposed on the technet script gallery that should show me “How to export all URLs of Firefox tabs at once”.

I said “should” because it actually doesn’t always work on my computer and there’s no error handling in any way in the proposed code 😦
firefox-tabs-01

First, to save you the hassle of trying that script, you can just open Tools/Options in Firefox and set the following. Even if you’ve 1000 tabs, the browser will open in a few minutes and restore the gazillion tabs without you to have to temporarily record the URL of each Firefox tabs that you want to open in the next session:firefox-tabs-02

Back to the proposed script. It actually threw the following error:
Error during serialization or deserialization using the JSON JavaScriptSerializer. The length of the string exceeds the value set on the maxJsonLength property.
Why? Because when you have a gazillion of tabs (whatever that means), your Mozilla .js file that stores the JSON data about your tabs can be quite big.
I found the following post in the Windows PowerShell forum that made me go this way:

Quick tip: change ConfigMgr updates maximum run time

With the new servicing model being applied to pre-Windows 10 operating systems, it’s a good idea to change the default “maximum run time” of the monthly cumulative updates.
For example, I’ve:
configmgr-max-run-time

Here’s a way to achieve this easily with PowerShell.
The -Name parameter of the Get-CMSoftwareUpdate accepts wildcards although its documentation says the opposite.
The -Fast is explained in the following warning message:

WARNING: ‘Get-CMSoftwareUpdate’ supports -Fast for retrieving objects without loading lazy properties. Loading lazy properties can cause significant performance penalties. If it is not necessary to utilize the lazy properties in the returned object(s), -Fast should be used. This warning can be disabled by setting $CMPSSuppressFastNotUsedCheck = $true.

# 1. View what updates will be targeted:
Get-CMSoftwareUpdate -Fast -Name "*Security Monthly Quality Rollup for Windows*" |
Select Max*,*DisplayName
#NB: MaxExecutionTime is in seconds in this case

# 2. Set the maximum run time to 120 minutes
Get-CMSoftwareUpdate -Fast -Name "*Security Monthly Quality Rollup for Windows*"  | 
Set-CMSoftwareUpdate -MaximumExecutionMins 120 -Verbose

Local security policy using Pester

In my previous post, I’ve shown how to use DSC to configure the local security policy. If you only want to get the compliance status of the system, Pester might be more suitable.

I first create a hastable of settings like this:

# secedit.exe /export /Cfg C:\secpol.txt /areas SECURITYPOLICY
(Get-Content -Path 'C:\secpol.txt' -ReadCount 1) `
-match '^([A-Z\s0-9_\\]+)=(.*)$' -replace '=',',' |
ConvertFrom-Csv -Header Key,Value1,Value2 | ForEach-Object {
    '    @{'
    "        Key = '{0}'" -f $_.Key
    "        Value1 = '{0}'" -f $_.Value1
    if ($_.Value2) {
    "        Value2 = '{0}'" -f $_.Value2
    }
    '    },'
}

and I populate the file secpol.ps1 using the output of the above command.
The content looks like this. The hashtable is stored inside an array named $SecurityPolicy.
secpol-compliance-01
secpol-compliance-02

Now in the test file secpol.tests.ps1, I’ve:

Let’s see how to use it

Invoke-Pester ~/documents/pester/secpol

secpol-compliance-03

Fine, my system is compliant.
But, let’s I change the Maximum password age using gpedit.msc
secpol-compliance-04

Now, Pester reports my system as not being compliant:
secpol-compliance-05
But, the only drawback with my quick’n dirty code is that it doesn’t tell you what value was found instead of the expected 42 in this case.

Audit policy using Pester

In my previous post, I’ve shown how to leverage DSC to apply a configuration that defines the local Audit policy.
If you’re just interested in the compliance of a system, Pester might be more suitable to assess it against a template. It will somehow validate the operational status of the system.

First, I create a hashtable of settings like this:

auditpol.exe /get /category:* /r |
ConvertFrom-Csv |
Select Subcategory*,*lusion* | 
ForEach-Object {
    '    @{'
    "        Name = '{0}'" -f $_.Subcategory
    "        GUID = '{0}'" -f $_.'Subcategory GUID'
    "        Inclusion = '{0}'" -f $_.'Inclusion Setting'
    '    },'
}

and I populate the file auditpol.ps1 using the output of the above command. The content looks like this. The hashtable is stored inside an array named $AuditPolicy.

Now in the test file auditpol.tests.ps1, I’ve:

Let’s see how to use it

Invoke-Pester ~/documents/pester/auditpol

audit-pol-invoke-pester-success

If I change the Logoff policy for example like this:

auditpol /Set /subcategory:{0CCE9216-69AE-11D9-BED3-505054503030} /failure:enable

and run the pester test a second time, I’ll get:
audit-pol-invoke-pester-failure
…my system isn’t compliant anymore with the settings defined in my template.

#PowerShell10Year

PowerShell celebrated its 10th anniversary on Monday, November 14th.

To celebrate it, there was a live stream all day long that was announced on the PowerShell Team blog

If you missed it, no problem, you can go to channel9 an watch it using this link https://channel9.msdn.com/Events/PowerShell-Team/PowerShell-10-Year-Anniversary

ps10y-itpro

The community demonstrated all day long how they use PowerShell. That was awesome!!! And Kenneth Hansen and Angel Calvo discussed Future Directions for PowerShell
ps10y-future-03

There were also Code Golf holes to celebrate that day 😀

  • Code golf hole 1
  • I submitted the following answer and introduced a old trick (works in PS2.0) to subtract days to the current datetime object

    gcim(gwmi -li *ix*).Name|? I*n -gt((date)+-30d)
    

    and it passed the pester test
    ps10y-hole1-pester
    Here’s what it does:

    • gwmi is the alias of the Get-WmiObject cmdlet.
    • -li is the shortest version of the -List parameter of Get-WmiObject.
    • -List allows wildcards when looking for WMI classes. So *ix* matches the Win32_QuickFixEngineering WMI class that the Get-Hotfix cmdlet queries.
    • gcim is the alias of the Get-CimInstance cmdlet.
    • (gwmi -li *ix*).Name returns Win32_QuickFixEngineering.
    • Now that we have the list of hotfixes as CIM instances we can filter on the right.
    • ? is the alias of Where-Object.
    • I*n is the short name of the InstalledOn property that is a datetime object.
    • So we can compare it to the current date minus 30 days.
    • We can omit Get- in Get-Date and just type (date).
    • To subtract 30 days we use the old trick (date)+-30d 😎

    A longer form would be

    Get-CimInstance (Get-WmiObject -List *ix*).Name |
    Where InstalledOn -gt (Get-date).AddDays(-30d)
    # or 
    Get-CimInstance Win32_QuickFixEngineering |
    Where InstalledOn -gt (Get-date).AddDays(-30d)
    
  • Code golf hole 2
  • I submitted the following answer that uses the -File switch parameter. Some answers submitted have a problem and may be broken when you change the path to another drive like HKLM: or Cert: for example. Mine is also somehow broken and works only if the console is started as administrator where the default path is set to C:\windows\system32.

    (ls c: -File|% E*n|group|sort c* -d)[0..9]
    

    …but it passed the pester test
    ps10y-hole2-pester
    Anyway, here’s how to decode it:

    • ls is the alias of the Get-ChildItem cmdlet.
    • ls c: -File will return only files including those that don’t have an extension in system32.
    • % is the alias of the ForEach-Object cmdlet.
    • E*n is the short name of the Extension property of items returned by Get-ChildItem.
    • Group is the short version of the Group-Object cmdlet. We can usually omit the -Object (Noun) for cmdlets that deal with -Object except for the New-Object cmdlet.
    • sort is the alias of the Sort-Object cmdlet.
    • c* is the short name of the Count property returned by Group-Object.
    • -d is the short name of the -Descending switch parameter of the Sort-Object cmdlet.
    • To get only the first 10, we enclose everything in parentheses to treat it as an array and then we enumerate the elements in the array using the [0..9] notation.

    A longer form would be

    (Get-ChildItem -Path c: -File | ForEach-Object { 
     $_.Extension 
    } |Group-Object | 
    Sort-Object -Property Count -Descending)[0..9]
    
  • Code golf hole 3
  • For the 3rd hole, I submitted the following solution 😎

    gal ?,?? -e h,g?,?s
    

    and it passed the pester test
    ps10y-hole3-pester
    Here is how to read it:

    • gal is the alias of the Get-Alias cmdlet.
    • Get-Alias uses by the default the -Name parameter and it accepts an array of strings and wildcards.
    • * represents all/any characters and ? only one character (it’s the same in DOS) and ?? represents two characters.
    • -e is the short name of the -Exclude parameter of the Get-Alias cmdlet.
    • -Exclude also accepts an array of strings and wildcards.
    • To avoid aliases for Get- cmdlets, we explicitly exclude h, the alias of the Get-History cmdlet, all the aliases for Get- cmdlets that begin by g and followed by a second letter like gi (Get-Item), gc (Get-Content),…, and finally the last two Unix aliases of the Get-Process cmdlet, ps, and the Get-ChildItem cmdlet, ls.

    A longer form would be

    Get-Alias -Name ?,?? -Exclude h,gc,gi,gl,gm,gp,gu,gv,ps,ls
    

Security policy and DSC

When I showcased DSC to our security team, I also built another wrapper of secedit.exe.
I took the same quick’n dirty approach as the audit policy DSC script from my previous post. Again, only a File and a Script DSC resources are involved in the configuration.

Note that there’s also a limitation in my code.
Secedit.exe can handle more than just the local security policy.
There are other areas it can cover: restricted group settings, user logon rights,

To get the security baseline I first exported the local security policy to a file like this:

 
secedit.exe /export /Cfg C:\secpol.txt /areas SECURITYPOLICY

… and I copied/pasted the content of the resulting C:\secpol.txt into to Content property of my File resource.