Force Intune apps to redeploy

Force Intune apps to redeploy

Last month, I had an app that had some issues for a single end user. I wasn’t sure why it was causing issues, but one of the troubleshooting steps we needed to do was uninstall it and have Intune reinstall it. We uninstalled the application. However, Intune, being Intune, sat there. We forced a sync, and nothing. I wish there was a redeploy option in the Intune interface, but there isn’t. So what can you do? Well, there is a small secret. Intune has registry keys that keep track of the deployments on the machine itself. These linger even after uninstalling the app. So, removing them is the gravey. So today we are going to force Intune apps to redeploy.

Intune Registry Keys / App ID Number

Intune’s registry keys are located in the Local Machine > Software > Microsoft > IntuneManagementExtension > Win32App. Let me tell you what now. My southern is going to come out some yall. This is where we can see the users. The system has it’s own user, which is the all zeros, but each other user has it’s own code.

When you open this folder, you will be taken to a beautiful list of what? Yeah, it’s a mess. You need to know some things about this list in order to force intune apps to redeploy. You will need to have the app’s ID number. To get this number, you will need to navigate to your Intune. We will be heading to the app you want to uninstall. I’m doing my 7zip today as an example. At the end of the url, you will see the appID. That’s what you will need.

Once you have that code, you will be ready. What you will need to do now is delete the folder with that code. Then navigate to the GRS folder. It will have a bunch of hashes. No, not the drug, but math code. Wait, is hash still what people call it now days? I feel old. Anyway, you have two options here. One, you can go to the logs and search the logs for the hash. This can take a while, and shockingly, it is not reliable as logs get deleted. The other way is to go through this registry folder, folder by folder, until you find the key, as seen below. I prefer PowerShell. Once you delete the required registry keys, all you have to do is restart the Microsoft Intune Management Extension service.

Powershell To the Rescue

If you have read this blog long enough, you know PowerShell is coming somehow. Today’s script will save you a crap ton of time. Let’s dive right in.

$Path = "HKLM:\SOFTWARE\Microsoft\IntuneManagementExtension\Win32Apps"
$AppID = "Your-App-Code-Goes-Here"

$Users = (Get-ChildItem -Path "$Path").name | Where-Object {($_ -like "*-*-*-*-*") -and ($_ -notlike "*00000000-0000-0000-0000-*")}

foreach ($user in $Users) {
    $Name = $User -replace "HKEY_LOCAL_MACHINE","HKLM:"
    $UserID = $user.split("\")[-1]
    $Applications = Get-ChildItem -Path $Name | Where-Object {$_.name -like "*$($AppID)*"}
    foreach ($App in $Applications) {
        $AppName = $App -replace "HKEY_LOCAL_MACHINE","HKLM:"
        Write-Host "App Name: $AppName"
        remove-item -Path $AppName -Recurse -Verbose -force
    }
    $GRSPath = "HKLM:\SOFTWARE\Microsoft\IntuneManagementExtension\Win32Apps\$UserID\GRS"
    $GRSes = Get-childitem -path $GRSPath
    foreach ($GRS in $GRSes) {
        $GRSProps = $GRS | Get-ItemProperty
        $Count = $GRSProps.psobject.Properties.count 
        if ($Count.count -gt 5) {
            $TotalKey = $GRSProps.psobject.Properties.name | where-object {$_ -like "*-*-*-*-*"}
            if ($TotalKey -like "*$($AppID)*") {
                $PathToRemove = $GRS.name -replace "HKEY_LOCAL_MACHINE","HKLM:"
                Remove-Item -Path $PathToRemove -Recurse -Force -Verbose
            }
        }
    }
}
Get-Service -DisplayName "Microsoft Intune Management Extension" | Restart-Service -Verbose

There are many versions online for this script. Most use the logs, and that’s cool. This script doesn’t use the logs, and for a good cause. In my case, the logs were deleted. Why were they deleted, you shall ask? Humans, that’s always going to be my answer until it’s AI.

The break down

Let’s break this bad boy down, shall we? The first part of the script is the path we are going to be playing with, followed by the code of the app. You will have to grab this from your intune.

$Path = "HKLM:\SOFTWARE\Microsoft\IntuneManagementExtension\Win32Apps"
$AppID = "Your-App-Code-Goes-Here"

Next, we want to grab all the users. So, remember I said the system uses all zeros. Well, we want to exclude those. However, users use the hypens. It’s the Fantastic 4, hypens, not the Marvel characters. Using a basic where object, we sort through all of the ones that have our hypens and are not the system and drop their ID numbers into the users variable.

$Users = (Get-ChildItem -Path "$Path").name | Where-Object {($_ -like "*-*-*-*") -and ($_ -notlike "*00000000-0000-0000-0000-*")}

Handling the App Side

Now we start our loop. Everyone should like a good loop. Each user will have it’s own path. The first thing we run into is that the above command gave us HKEY_Local_Machine instead of a searchable HKLM. So we change them using the replace. Then we grab the userID for later. Finally, we grab all the applications. Notice the name is the new name we made. It’s important to have the HKLM: because without it, you will get an error with get-childitem.
No candy was stolen from any children while writing this blog post.

    $Name = $User -replace "HKEY_LOCAL_MACHINE","HKLM:"
    $UserID = $user.split("\")[-1]
    $Applications = Get-ChildItem -Path $Name | Where-Object {$_.name -like "*$($AppID)*"}

Notice we are looking for the appid at the end. Sometimes, there will be more than one entry like this. To force Intune apps to redeploy, we must remove all of them. I liken them to bed bugs. Burn them all. With that said, we start our loop. For each App inside the applications. We will get the app name and then remove it. Once again, we used get-childitem. Goodness, I need to stop still items from kids. So we need to convert the name like we did before changing the HKEY_Local_machine to HKLM: with a nice replace. Once we have it, we delete the path and everything inside by force.

foreach ($App in $Applications) {
        $AppName = $App -replace "HKEY_LOCAL_MACHINE","HKLM:"
        Write-Host "App Name: $AppName"
        remove-item -Path $AppName -Recurse -Verbose
 }

Handling GRS Side

Now we need to handle the GRS side. The GRS keeps the datetime stamps. Like I said before, most people use the logs. Today we will navigate through the registry. The first thing we are going to do is set the path and get the kids on that path. This is where the UserID we made at the start of this big loop comes into play.

$GRSPath = "HKLM:\SOFTWARE\Microsoft\IntuneManagementExtension\Win32Apps\$UserID\GRS"
$GRSes = Get-childitem -path $GRSPath

Now we have the children’s items. We start our looping. The first thing we get is our GRS properties with the get-itemproperty commands. Now here is the magic. A standard check has only 1 or maybe 2 items inside the folder. While more advanced items will have more than that. So, if we convert the properties into a Powershell object, we can count them.

$GRSProps = $GRS | Get-ItemProperty
$Count = $GRSProps.psobject.Properties.count 

Yes, the second line works. You can pretty much convert anything into a PowerShell object. All we have to do now is count how many counts per object are there. When we convert the item property into a powershell object, we gain a few extra items. So, anything past 5 in this case will be our special stuff. So, if it is past 5, we get to work.

We first look at the keys, looking for our fantastic 4. We will do this by calling the psobject.properties.name because it will be the name of the property. Then we will compare it to the appid. If they are the same, we correct the hkey_local_machine and drop our nuke, remove-item. Nested ifs are fun, but can get complex quick if you don’t watch out.

if ($Count.count -gt 5) {
        $TotalKey = $GRSProps.psobject.Properties.name | where-object {$_ -like "*-*-*-*-*"}
        if ($TotalKey -like "*$($AppID)*") {
            $PathToRemove = $GRS.name -replace "HKEY_LOCAL_MACHINE","HKLM:"
            Remove-Item -Path $PathToRemove -Recurse -Force -Verbose
        }
}

The GRS has been removed after this.

Restarting the service

After the large loop of Fantastic Four, we have to restart the intune extension. So, using get service, we pipe it into restart service. Then we are done! Right? Well, kind of.

Get-Service -DisplayName "Microsoft Intune Management Extension" | Restart-Service -Verbose

Final Step

After the script has done it’s thing and stolen candy for kids and nuked stuff, you will need to resync the computer. You can do this via the Accounts setting, or you can do this via Intune. In my case, the application we were redeploying was our remote application. So, I had to do it via Intune.

Navigate to the device under Windows > Device Name and hit the sync button. Now you are done. Force Intune apps to redeploy, check.

What can we learn as a person?

Restarting is sometimes the only option. Taking a step back, clearing things away, and starting new is good, whether you’re troubleshooting an Intune app deployment or dealing with a hard time in life.

When an app in Intune stops working, we don’t just sit around and hope it gets fixed (at least for a while). After we empty the registry and do some troubleshooting, we gently push it to redeploy. Life is no different. When things don’t work out the way you expected, that’s okay; setbacks are inevitable. Starting over equalizes the situation; it’s not a sign of surrender.

Restarts, in reality, are chances for growth. By doing so, they demonstrate our flexibility, competence, determination and insight to put things right. Our fantasic four. When something feels stuck, whether it’s an app or your thinking, don’t be scared to reset. Do not be afraid, especially with our thinking. That’s where real change happens.

Search PowerShell Objects

Search PowerShell Objects

Recently, I was playing with Intune devices using graph API and managed devices. I was able to search for things like the device name with no issues, but when it came to the unique codes, I started having beautiful dyslexia issues. As I struggled, I asked myself how I could search this whole PowerShell object all at once, every value and every index. The first thought was to go property by property with a switch. But I asked myself, How can I reuse this function later? Let’s Search PowerShell Objects together.

The Script

function Search-Object {
    param (
        [Parameter(Mandatory=$true)][pscustomobject]$Object,
        [Parameter(Mandatory=$true)][string]$SearchString,
        [switch]$Expand
    )
    $Results = $Object | ForEach-Object -Begin { $index = 0 } -Process {
        $_.PSObject.Properties | ForEach-Object {
            if ($_.Value -like "*$SearchString*") {
                [pscustomobject][ordered]@{
                    Index = $index
                    PropertyName = $_.Name
                    Value = $_.Value
                }
            }
        }
        $index++
    }
    if ($Expand) {
        $IndexesToPresent = ($results | group-object -Property index).name
        foreach ($itp in $IndexesToPresent) {
            $Object[$itp]
        }
    } else {
        $Results
    }
}

The Breakdown

The concept is that we feed the script the ps object we want to search in. This function will search the top level. Searching recursively is dangerous for weaker machines, but it’s fully doable with the above function. It’s time to break down this function.

Parameters

First, we want to look at the parameters. We need the Powershell object, then the string, and finally a nice little flag to give us more information. We are requiring the object and the search are both required because that’s what we need. The expand isn’t because we may not need everything.

The Search PowerShell Object Loop

This step is the meat and potato of the function. Here is how we are going to search the full object. We start off by looping the object. We want to use a foreach-object loop because of the beginning and simplicity of it all.

The foreach-object is treated like a function in some context. We have a beginning, process and ending flags. So we begin the loop with our index of zero. We do this to keep track of the index of the powershell object we are analizing. The process is where we begin.

$Results = $Object | ForEach-Object -Begin { $index = 0 } -Process {}

The next step is to open the door of the PSobject. Once we crack the door we select the Properties information and start another loop through that.

$_.PSObject.Properties | ForEach-Object {}

This is where we can now search each object inside the object. At this point we are looking at a properity of an object that has been opened that is inside the main object. Confused yet, that’s ok. We are at the end of that trail. Next, we need to ask if the value of the current property is our search string. If it’s true, then we send back a ps object.

if ($_.Value -like "*$SearchString*") {
    [pscustomobject][ordered]@{
        Index = $index
        PropertyName = $_.Name
        Value = $_.Value
    }
}

Objects making objects, we are so in the .net work for sure.

What’s cool at this is the ps object is being returned to the Results. Finally, we do an index increase by using the index++.

$index++

Expanding

Now we have searched the powershell object, lets expand it. The code above grabs the index number. So we want to expand upon that. If you trigger the flag “Expand” we want to grab all the index number. First we take the results from the above and group them by group-object. We select the property of index. This will give us all of the options in a nice pretty package. So we then select the names. This will give us all the indexes for each item that was found and not multiple of the same. I

From there we start a for each loop. Here we display the objects index. We do this by having the main object with [#]. If expanding isn’t set, we just display the results.

if ($Expand) {
        $IndexesToPresent = ($results | group-object -Property index).name
        foreach ($itp in $IndexesToPresent) {
            $Object[$itp]
        }
    } else {
        $Results
    }

Multiople levels

Since this function does one level, you can use another powershell script that will search each level if it has an object. The only problem with this is, processing power. Powershell is not designed to be database software. So it’s best not to deep dive into an object like that.

What can we learn as a person?

It’s ok to find other solutions. Doing it the hard way only adds stress. But there is a point where you can spend more time automating than doing the work itself. So keep it balanced. You deserve that. Just like we search PowerShell objects, we should search for different ways to keep the balance in our life.

Additional Reading

Get Mailbox Rules Using PowerShell

Get Mailbox Rules Using PowerShell

Ever found yourself tangled in the web of Exchange Online mailbox rules? Yeah, me too. It’s like trying to find a needle in a haystack, especially if you’re managing multiple mailboxes. Thankfully, I stumbled upon a nifty PowerShell script that makes this task a breeze. Let’s Get Mailbox Rules Using PowerShell.

Let’s Talk About Our Script

Before we jump into the script, let’s understand what it does. The PowerShell script Get-RASMailboxRules helps you retrieve mailbox rules for specific email addresses in Exchange Online. Whether you’re an IT admin juggling a dozen tasks or just someone who likes things neat and tidy, this script can save you a ton of time.

Breaking Down the Script

Here’s the full script for reference:

function Get-RASMailboxRules {
    [cmdletbinding()]
    param (
        [Parameter(
            ValueFromPipeline = $True,
            ValueFromPipelineByPropertyName = $True,
            HelpMessage = "Email Addresses",
            Mandatory = $true)][Alias('Mailbox','EmailAddress')][String[]]$Mailboxes
    )
    begin {
        # Checks if Exchange Online is connected
        if ($null -eq (Get-ConnectionInformation)) {Connect-ExchangeOnline}

        # Pulls all mailboxes from the $Mailboxes parameter and checks if they exist
        $Boxes = @()
        foreach ($box in $mailboxes) {
            Try {
                $Boxes += Get-Mailbox $box
            } catch {
                Write-Error "Error getting mailbox"
            }
        }
    }
    process {
        foreach ($mailbox in $Boxes) {
            $Rules = Get-InboxRule -Mailbox $mailbox.Name

            foreach ($Rule in $Rules) {
                $ruleDescription = $Rule.Description -join "`n"
                $Description = (($ruleDescription -split 'If the message:')[1] -split 'Take the following actions:')
                $ifMessage = ($Description[0].Trim() -replace "`t", "") -replace "\s*`n", ""
                $actions = ($Description[1].Trim() -replace "`t", "") -replace "\s*`n", ""

                [PSCustomObject]@{
                    MailboxName = $Mailbox.Name
                    Mailbox = $Mailbox.UserPrincipalName
                    RuleName = $Rule.Name
                    Enabled = $Rule.Enabled
                    ID = $Rule.RuleIdentity
                    IfMessage = $ifMessage
                    Actions = $actions
                }
            }
        }
    }
    end {
        Disconnect-ExchangeOnline -Confirm:$false
    }
}

What’s Happening Here?

Let’s break it down:

  1. Parameters and Initialization:
    • The script takes email addresses as input through the $Mailboxes parameter.
    • It checks if Exchange Online is connected. If not, it connects using Connect-ExchangeOnline.
  2. Fetching Mailboxes:
    • It loops through the provided mailboxes and tries to fetch their details using Get-Mailbox.
    • Any errors encountered during this process are caught and reported.
  3. Processing Mailbox Rules:
    • For each mailbox, it retrieves the inbox rules using Get-InboxRule.
    • It parses the rules to extract the conditions (IfMessage) and actions (Actions).
  4. Output:
    • It creates a custom PowerShell object for each rule, which includes details like mailbox name, rule name, enabled status, and more.
    • Finally, it disconnects from Exchange Online to clean up.

Key Points to Remember

  • Mandatory Parameter: The script requires at least one email address to be provided.
  • Error Handling: It gracefully handles errors when fetching mailbox details.
  • Custom Output: The output is a clean, readable list of mailbox rules with all the necessary details.

Wrapping up “Get Mailbox Rules using PowerShell”

And there you have it! A super handy PowerShell script to get mailbox rules using PowerShell in order. It’s efficient, straightforward, and takes the hassle out of managing mailbox rules. So next time you’re knee-deep in inbox rules, you know which script to pull out.

Happy scripting, folks! If you run into any issues or have questions, drop them in the comments below. Let’s keep the conversation going!

Additional Reading

    Scheduled Tasks with PowerShell

    Scheduled Tasks with PowerShell

    Last week we went over how to do audits using PowerShell (Link). Today we will use scheduled tasks with PowerShell to have the audit script run hour by hour. We do this because we don’t want to be manually running the PowerShell script every hour. Let the computer handle all of that for us. We will go over how to manually build the Scheduled Task and the PowerShell way.

    Manual Process – Scheduled Tasks

    Lets take a look at the manual process. We are placing our AuditDisabledAccounts.ps1 script on the computer. I like placing things in the c:\scripts or c:\temp folder. Sometimes this is good, sometimes this is bad. It depends on the world you are working in.

    1. Start Task Scheduler
    2. Click Task Scheduler Library.
    3. Right Click and select basic task
    4. Name it accordingly. I am naming mine “Hourly Disabled AD Audit.”
    5. Under Triggers, I selected When the computer starts.
      • This scheduled task will repeat itself with another setting. It’s best to get it started when the computer starts. This way if the system restarts, it will start again. It can become confusing over time.
    6. The action will be start a program
      • Program: Powershell
      • Arguments: -NoProfile -ExecutionPolicy Bypass -HoursBack 1 -Servers AD1,AD2,AD3 -OutCSVfile “C:\Reports\DisabledAccountsAudit.csv”
      • Start In: c:\temp\AuditDisabledAccounts.ps1
    7. To finish, you want to open the properties dialog

    Now we have a basic scheduled task setup. Next we want to have it trigger every hour. Sense we opened the properites you can now do just this.

    1. On the general tab
    2. Radio check: “Run whether the user is logged on or not.”
      • If you need to change the user, this is where you will do that.
    3. Click the Triggers tab.
    4. You will see at startup, click edit
    5. Under advanced Settings
      • Check Repeat task every
      • Select 1 hour
      • Duration: Indefinitely
    6. Click ok

    That’s how you manually setup a Scheduled Task for PowerShell.

    Powershell Method

    Now we can do a Scheduled Tasks with PowerShell. We will be using the scheduledtask commands to create the task accordingly. Lets take a look at the script itself.

    The script – Scheduled Tasks with PowerShell

    # Variables
    $ScriptPath = "C:\temp\AuditDisabledAccounts.ps1"
    $TaskName = "Audit Disabled Accounts"
    $OutCSVfile = "C:\Reports\DisabledAccountsAudit.csv"
    $Servers = "AD1,AD2,AD3"
    $HoursBack = 1
    $User = Read-Host -Prompt "Domain\Username"
    $Creds = Read-Host -AsSecureString -Prompt "Enter Password" 
    
    $BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($Creds)
    $UnsecurePassword = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR)
    [Runtime.InteropServices.Marshal]::ZeroFreeBSTR($BSTR)
    
    
    $triggers = 0..23 | ForEach-Object {
        New-ScheduledTaskTrigger -At "$($_):00" -Daily
    }
    
    
    $principal = New-ScheduledTaskPrincipal `
        -id 'Author' `
        -UserId "$User" `
        -LogonType Password `
        -RunLevel Limited
        
    
    $Action = New-ScheduledTaskAction `
        -Execute "PowerShell" `
        -Argument "-NoProfile -ExecutionPolicy Bypass -File `"$ScriptPath`" -HoursBack $HoursBack -Servers $Servers -OutCSVfile `"$OutCSVfile`"" `
        -WorkingDirectory 'C:\temp\'
    
    $Task = New-ScheduledTask `
        -Description 'Usered To Audit Disabled Accounts' `
        -Action $Action `
        -Principal $principal `
        -Trigger $triggers
    
    Register-ScheduledTask `
        -TaskName "$TaskName" `
        -TaskPath '\' `
        -Action $Action `
        -Trigger $triggers `
        -User $User `
        -Password "$UnsecurePassword"
    

    The breakdown

    The first thing we do is setup. We want to have the script, the name, the out file for our audit report, our servers, and the hours back we want to go.

    Veriables

    # Variables
    $ScriptPath = "C:\temp\AuditDisabledAccounts.ps1"
    $TaskName = "Audit Disabled Accounts"
    $OutCSVfile = "C:\Reports\DisabledAccountsAudit.csv"
    $Servers = "AD1,AD2,AD3"
    $HoursBack = 1
    $User = Read-Host -Prompt "Domain\Username"
    $Creds = Read-Host -AsSecureString -Prompt "Enter Password" 
    

    The first thing we want is the veriables. We want the path of the script. We want it’s name. Where our CSV files will be dropped, the servers, how many hours back, usernames and passwords. Notice that the User is using a read-host and creds is using a secure string. This is to help stop shoulder surfers and powershell memory. This way you password isn’t passed around. Basicly, we input the password as a secure string, and it becomes a veraible. Thus, if someone is looking through the powershell history, or is monitoring it with something like defender, then they will not see the password. Only the veraible from this point on.

    Decoding Passwords as Veriables

    Part of the Scheduled Tasks with PowerShell is we need to register the task later. This means that the password needs to be plain text. However, we don’t want a password to ever exist in the shell visability. So we want to decode it directly into a Veriable.

    $BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($Creds)
    $UnsecurePassword = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR)
    [Runtime.InteropServices.Marshal]::ZeroFreeBSTR($BSTR)
    

    The code above allows you to convert the secure string to normal text in Powershell 5. If you are using powershell 7, this isn’t a problem. But most servers are still defaulting at 5. The new veriable name is UnsecurePassword which has the password as plain text for the register command.

    Triggers – Scheduled Task for powershell

    We need to start making the triggers. Unlike the gui, we can’t setup a startup with a hourly repeat. Instead, the safeist way is to do an hourly thing for repeating the hour. We do this using the new-scheduledtasktrigger command.

    $triggers = 0..23 | ForEach-Object {
        New-ScheduledTaskTrigger -At "$($_):00" -Daily
    }
    

    Since we have 24 hours in a day, we want to repeate a foreach-object loop 24 times. We start at 0 and go to 23 which makes 24. Wow… Anyways, As we loop, the $_ will be the number. So we create a new trigger at that time and set it to daily. All of this will be dumped into the $triggers array.

    Principal

    Next we want to setup a user account. The command for this is…. Yep, you guessed it, New-ScheduledTaskPrincipal. Here we are setting the ID to the author, using our user flag, doing the logontype as password, and the runlevel is limited. We don’t want it to have full access to anything since it’s not doing anything on the local PC. Notice the ` symbol. This allows you to do mulitlple lines with one command. It’s like break here and continue to the next line. It makes reading code so much easier.

    $principal = New-ScheduledTaskPrincipal `
        -id 'Author' `
        -UserId "$User" `
        -LogonType Password `
        -RunLevel Limited
    

    Actions

    Next we need to do our actions. AKA, what’s it going to do. Using the New-scheduledTaskAction we want to execute with powershell and push our arguments in. Using our Veriables, we fill in the blanks. It’s very straight forward. The secret sause here is the arguments will be like you did with the gui approach.

    $Action = New-ScheduledTaskAction `
        -Execute "PowerShell" `
        -Argument "-NoProfile -ExecutionPolicy Bypass -File `"$ScriptPath`" -HoursBack $HoursBack -Servers $Servers -OutCSVfile `"$OutCSVfile`"" `
        -WorkingDirectory 'C:\temp\'
    

    Tasks

    Next we need to make the task itself. We are going to use the New-ScheduledTask command. This part of the command creates a task object that will need to be registered. We give it the description we want. The Actions from above. The user inside the principal names and the triggers we built out.

    $Task = New-ScheduledTask `
        -Description 'Usered To Audit Disabled Accounts' `
        -Action $Action `
        -Principal $principal `
        -Trigger $triggers
    

    Register The Task

    Finally, we want to register the task in question. We are going to use “Register-scheduledTask” to do this. Notice that this is where we are using that password we used at the start. It’s used as a variable, and thus it’s never shown in the PowerShell history.

    Register-ScheduledTask `
        -TaskName "$TaskName" `
        -TaskPath '\' `
        -Action $Action `
        -Trigger $triggers `
        -User $User `
        -Password "$UnsecurePassword"
    

    Additional Thoughts on Scheduled Tasks with PowerShell

    This technique is very powerful. I built out a script that scanned the local network via Get-NetNeighbor. The script was a scheduled task and it grabbed all the devices. Imagine having admin rights, pushing out a script that scans the local network drops a scheduled task on another computer that scans that network. You could map out a whole network within a few minutes. This could be used as a worm and it’s a good reason to block WMI on the network except from the machines that does the administration.

    What can we learn as a person?

    It’s always a good idea to have routine. Having a Scheduled task in your life that you like tends to improve our lives. For example, I like going to a monthly meetup with my friends. It’s something I look forward to. Having it on my calendar helps. This is why vacations are important. We need to have those things on our calendar. It’s ok to have them on the calendar. So, find something you can look forward to, and put it on the calendar.

    Additional Resources