Thursday, 14 May 2020

Converting between XML systemtime and number of hours or days with Powershell

I recently used an XML query in Powershell that looked like this:

$xmlQuery = @'
  <Query Id="0" Path="Security">
    <Select Path="Security">
*[System[(EventID=4624) and TimeCreated[timediff(@SystemTime) &lt;= 86400000]] and EventData[Data[@Name='IPAddress'] and (Data='')]]


The query was used to filter events from the event log that occurred within the last 24 hours.  However I needed to change this 7 days.  The unit of time is milliseconds but I wanted to make sure I had it exactly correct, so I checked it using the following commands:

$Start=[datetime]"01/01/2020 00:00"
$End=[datetime]"01/02/2020 00:00"
New-Timespan -Start $Start -End $End

This was my first command to check that I was using the right units of time.  This command returned:

Days              : 1
Hours             : 0
Minutes           : 0
Seconds           : 0
Milliseconds      : 0
Ticks             : 864000000000
TotalDays         : 1
TotalHours        : 24
TotalMinutes      : 1440
TotalSeconds      : 86400
TotalMilliseconds : 86400000

So I could confirm that milliseconds was the right unit of time - and you can see that 86400000 matches the 24 hour time difference I use in my XML query above.  So I wanted to confirm what 7 days would be:

$End=[datetime]"01/08/2020 00:00"
New-Timespan -Start $Start -End $End

Days              : 7
Hours             : 0
Minutes           : 0
Seconds           : 0
Milliseconds      : 0
Ticks             : 6048000000000
TotalDays         : 7
TotalHours        : 168
TotalMinutes      : 10080
TotalSeconds      : 604800
TotalMilliseconds : 604800000

So I could see that the number I needed to use in my query for a time difference of 7 days was 604800000.  So my new XML query would be:

$xmlQuery = @'
  <Query Id="0" Path="Security">
    <Select Path="Security">
*[System[(EventID=4624) and TimeCreated[timediff(@SystemTime) &lt;= 604800000]] and EventData[Data[@Name='IPAddress'] and (Data='')]]


Wednesday, 11 December 2019

Powershell - compare two mailboxes in hash table and export to CSV


I thought I’d share a useful little powershell trick I used today which allowed me to easily compare two very similar mailboxes in Office 365.  I had a user who had a duplicate mailbox which had been inadvertently created through an issue with AAD Sync.  I knew all the fields I would be comparing would be identical so I used a hash table with embedded arrays to compare the information I was interested in. 


First create a variable for each mailbox which will contain all the information we will be comparing.  Then create the hashtable.



$user1 =  get-mailbox -identity

$user2 =  get-mailbox -identity

$combined = @{}


Next pipe the fields from the variable into the hash table, creating an array for each one.  Then pipe the variables containing our mailbox data into the nested arrays in the hashtable.



$ | Foreach { $combined[$_.Name] = @() }

$ | Foreach { $combined[$_.Name] += $_.Value }

$ | Foreach { $combined[$_.Name] += $_.Value }



Finally I converted the hashtable back to a PSObject so that I could export it to a csv file and analyse the output in Excel.


$combined.getenumerator() | ForEach-Object {

    New-Object -Type PSObject -Property @   {

        'Field' = $_.Name

        '' = $_.Value[0]

        '' = $_.Value[1]


    } | Select-Object Field,, | Export-Csv C:\cloudwyse\comparison.csv -NoType



The whole (short) script is available in the Gist below.


Wednesday, 20 November 2019

SOLVED! RADIUS Authentication Failed (MSCHAP error: "E=649" R=0 V=3)

Client setup: Sonicwall NSA 4600 and NPS on Server 2016.

Error: RADIUS Authentication Failed (MSCHAP error: E=649 R=0 V=3)

Tried just about everything to fix this one. Gradually unpicked every single bit of security right back to clear password and nothing seemed to work.  In the end I went into the user's account and under 'Dial in' changed the setting from 'Control access through NPS Network Policy' to 'Allow Access'.  Finally I've had a successful authentication attempt for my test user.  Now I need to start dialling back up the security setting by setting and try and get to the bottom of what is going on.

Tuesday, 22 October 2019

Powershell script to find scheduled tasks on all servers or computers in the domain.


First off lets get all the set up variables out of the way.  A lot of these are the same variables I use in every script and I just copy them in each time so you may not want to use them all.



$Transpath = $env:LOCALAPPDATA + "\Cloudwyse\Logs\scheduled_task_Report_" + $(get-date -Format ddMMyy_HHmmss) + ".log"

Start-Transcript -Path $TransPath -NoClobber

$DateTime = (Get-Date).ToString('ddMMyy_HHmmss')

$ResultsPath = "C:\Cloudwyse\Scripts\Output\Scheduled_Task_Test_Output_" + $DateTime + ".csv"

$Date = Get-Date -Format "dd-MM-yyyy"

$Pass = cat C:\mysecurestring.txt | convertto-securestring

$Cred = new-object -typename System.Management.Automation.PSCredential -argumentlist "CONTOSO\rick.sanchez",$Pass

$DC = ""

$TaskReport = @()

$TestedServers = 0

$OutputPath = "C:\Cloudwyse\Scripts\Output"

$SplitSeparator = "\"

$SplitOption = [System.StringSplitOptions]::RemoveEmptyEntries


I like to use Write-Output to add the actions to the logfile (transcript) started in the previous section.  I use this instead of comments as it does the same job but also makes reading the logs easier.  I also prefer it to Write-Host since the latter is considered bad practise.


First we’re just going to make sure that the folder required for the logs already exists, and create it if not.




Write-Output "Script started at $DateTime"

Write-Output "Checking whether report destination patch exists..."


if (!(Test-Path $OutputPath)) {

    Write-Output "$OutputPath does not exist, creating it now..."

    New-Item -ItemType Directory -Force -Path $OutputPath

    } else {

    Write-Output "$OutputPath already exists, continuing with script..."





Next make a connection to the DC (I prefer doing this than running scripts on the DC itself.  Once this is done, run the LDAP query below to get all the servers in our environment.



Write-Output "Establishing powershell session with $DC"

$TargetSession = New-PSSession -ComputerName $DC -Credential $Cred

Invoke-Command $TargetSession -Scriptblock {Import-Module ActiveDirectory}

Import-PSSession $TargetSession -Module ActiveDirectory | out-null


$serverlist = (Get-ADComputer -LDAPFilter "(&(objectcategory=computer)(OperatingSystem=*server*))").Name

Write-Output "Server list is as follows: `r $serverlist"

Write-Output "Querying $($serverlist.count) servers that were found with the LDAP Query"




Then we get into our foreach loop that is going to check through every server in the list.  I’ve added a variable I’m calling $Skip.  This jut allows me to speed of the execution of the script by skipping any servers that I know are going to fail.  So I use Try Catch to test that I can access the path to my scheduled tasks.  I’ve already trapped the error I need for unauthorised access and have it in my catch statement.  If this error is triggered then the $Skip flag will be set to avoid wasting time on that record.



foreach ($server in $serverlist) {

    $Skip = $False

    $ServerFQDN = $Server + ""

    Write-Output "Checking server $serverFQDN"

    $serverpath = "\\" + $serverFQDN + "\c$\Windows\System32\Tasks"

    Try     {

        Write-Output "Testing $ServerPath"

        $PathTest = Test-Path $ServerPath -ErrorAction Stop


    Catch [System.UnauthorizedAccessException]  {

        Write-Output "Access to $ServerPath was denied.  Setting skip flag for $ServerFQDN..."

        $Skip = $True


    if (!$Skip) {

        if ($PathTest -match "False") {

            Write-Output "Server not found, setting skip flag for $ServerFQDN..."

            $Skip = $True





The next step will only run if the $Skip flag is not set.  A list of tasks will be pulled together from the scheduled tasks library.  



    if (!$Skip) {

        $tasks = Get-ChildItem -Path $serverpath -File

        Write-Output "Tasks for $serverFQDN as follows: `r $tasks"

        $TestedServers = $TestedServers +1

        if ($tasks) {

            $TestedTasks = 0

            Write-Output "Script has found $($tasks.count) scheduled tasks for $serverFQDN"



The script now works through each task in the variable and gets the content.  If the task is not a system task it will be added to the hash table I’m using to generate the final list which will be exported using export-csv.  I’ve recently changed the way I build these hash tables as I realised I was using an outdated method from Powershell V1 days previously.  There’s a great article on the subject here.  


            foreach ($task in $tasks) {

                $TaskPath = $serverpath + "\" + $task.Name

                $task = [xml] (Get-Content $TaskPath)

                [STRING]$check = $task.Task.Principals.Principal.UserId

                $SplitTaskPath = $TaskPath.split($SplitSeparator,$SplitOption)


                if ($task.Task.Principals.Principal.UserId) {

                    $hash = @{

                    Server      =   $ServerFQDN

                    TaskName    =   $SplitTaskPath[(($SplitTaskPath.count) - 1)]

                    Command     =   $Task.task.actions.exec.command

                    Owner       =   $task.Task.Principals.Principal.UserId


                    $Build = New-Object PSObject -Property $hash


                    Write-Output "Adding a task to the list for $serverFQDN"

                    $TaskReport += $Build

                    $TestedTasks = $TestedTasks +1


                Write-Output "Added $TestedTasks of $($tasks.count) tasks to list for server $serverFQDN"


        } else {

            Write-Output "No tasks found for server $serverFQDN"


    Write-Output "All tests complete for server $server. $TestedServers out of $($serverlist.count) servers tested so far..."

    } else {

    $TestedServers = $TestedServers +1

    Write-Output "Skipping server $ServerFQDN. $TestedServers out of $($serverlist.count) servers tested so far..."





Finally the script exports the results to the path created earlier and cleans up after itself.  



Write-Output "Script has tested $TestedServers servers..."

Write-Output "Writing results to $ResultsPath"

$TaskReport | Export-CSV $ResultsPath

Write-Output "Removing Powershell session for $DC"

Remove-PSSession $TargetSession

Write-Output "Script complete... Goodbye! :)"




If you found the script helpful please leave a comment below or add a link to this article. Likewise if you notice any mistakes please share your experience.  The entire script is available below.  Good luck!