Identifying and restoring shortcuts to start menu from Microsoft incident MO497128

Disclaimer! Do this at your own risk. I do not take any responsibility in impact this may cause for you or your tenant.

Update: Now that Microsoft has released their own “fix” for this issue, I would advise to test that out first. Though their fix do not support all shortcuts, so this solution can be used to complement with other company specific applications.

Firstly, make sure that your ASR rule for Block Win32 API calls from Office macro has been changed to Audit mode. In the bottom of the article you can see how to verify it is reflected on the computer.

To identify what shortcuts were deleted in your tenant, run the following in Advanced Hunting

DeviceEvents
| where ActionType == "AsrOfficeMacroWin32ApiCallsBlocked" and Timestamp >= datetime("2023-01-13 00:00:00Z")
| order by Timestamp
| where FileName endswith ".lnk"
| summarize count() by FileName
| sort by count_

The output should be something similar to

Stable method

We have experienced that the most stable method of fixing shortcuts is using a modified version of Microsoft’s script using Proactive Remediations to run the script. This modified script checks if the shortcuts exist AND if they are pointing correctly – this so that it re-runs if there are any broken shortcuts. If one or more shortcuts are not “healthy” the remediation script is run.

You can add your own applications to the Detection and Remediation script by finding the correct names in the registry under “HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\”

$programs = @{
    "Adobe Acrobat"                = "Acrobat.exe"
    "[Adobe Photoshop]"            = "photoshop.exe"
    "[Adobe Illustrator]"          = "illustrator.exe"
    "Adobe Creative Cloud"         = "Creative Cloud.exe"
    "Adobe Substance 3D Painter"   = "Adobe Substance 3D Painter.exe"
    "Firefox Private Browsing"     = "private_browsing.exe"
    "Firefox"                      = "firefox.exe"
    "Google Chrome"                = "chrome.exe"
    "Microsoft Edge"               = "msedge.exe"
    "Notepad++"                    = "notepad++.exe"
    "Parallels Client"             = "APPServerClient.exe"
    "Remote Desktop"               = "msrdcw.exe"
    "TeamViewer"                   = "TeamViewer.exe"
    "[Royal TS]"                   = "royalts.exe"
    "Elgato StreamDeck"            = "StreamDeck.exe"
    "[Visual Studio]"              = "devenv.exe"
    "Visual Studio Code"           = "code.exe"
    "Camtasia Studio"              = "CamtasiaStudio.exe"
    "Camtasia Recorder"            = "CamtasiaRecorder.exe"
    "Jabra Direct"                 = "jabra-direct.exe"
    "7-Zip File Manager"           = "7zFM.exe"
    "Access"                       = "MSACCESS.EXE"
    "Excel"                        = "EXCEL.EXE"
    "OneDrive"                     = "onedrive.exe"
    "OneNote"                      = "ONENOTE.EXE"
    "Outlook"                      = "OUTLOOK.EXE"
    "PowerPoint"                   = "POWERPNT.EXE"
    "Project"                      = "WINPROJ.EXE"
    "Publisher"                    = "MSPUB.EXE"
    "Visio"                        = "VISIO.EXE"
    "Word"                         = "WINWORD.exe"
    "[PowerShell 7]"               = "pwsh.exe"
    "SQL Server Management Studio" = "ssms.exe"
    "Azure Data Studio"            = "azuredatastudio.exe"
    "Zoom"                         = "zoom.exe"
    "Skype for Business"           = "Skype.exe"
    "VLC Player"                   = "vlc.exe"   
    "Cisco Jabber"                 = "CiscoJabber.exe"
    "Microsoft Teams"              = "msteams.exe"
    "PuTTY"                        = "putty.exe"
    "wordpad"                      = "WORDPAD.EXE"
    "[AutoCAD]"                    = "acad.exe"
    "[CORSAIR iCUE Software]"      = "iCue.exe"
    "[Steam]"                      = "steam.exe"
    "Paint"                        = "mspaint.exe"
}

$LogFileName = "ShortcutRepairs.log";
$LogFilePath = "$env:temp\$LogFileName";

Function Log {
    param($message);
    $currenttime = Get-Date -format u;
    $outputstring = "[" + $currenttime + "] " + $message;
    $outputstring | Out-File $LogFilepath -Append;
}

Function LogAndConsole($message) {
    Write-Host $message -ForegroundColor Green
    Log $message
}

Function LogErrorAndConsole($message) {
    Write-Host $message -ForegroundColor Red
    Log $message
}

$programs.GetEnumerator() | ForEach-Object {
    $reg_path = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\$($_.Value)"
    try {
$apppath = $null
$target = $null
        try { $apppath = Get-ItemPropertyValue $reg_path -Name "Path" -ErrorAction SilentlyContinue } catch {}
if ($apppath -ne $null)
{
$target = $apppath + "\" + $_.Value
}
else
{
try { $target = Get-ItemPropertyValue $reg_path -Name "(default)" -ErrorAction SilentlyContinue } catch {}
}
        if ($target -ne $null) {
                
$target = $target.Trim("`"")
                $shortcut_path = "$env:PROGRAMDATA\Microsoft\Windows\Start Menu\Programs\$($_.Key).lnk"

                $sh = New-Object -ComObject WScript.Shell
                if(Test-Path($sh.CreateShortcut($shortcut_path).TargetPath)){
                    LogAndConsole ("Shortcut already present and pointing correctly for {0} in Start Menu" -f $_.Key)
                }
                else{
                    LogErrorAndConsole ("Shortcut not correct - starting for {0} - starting remediation" -f $_.Key)
                    Exit 1
                }
        }
    }
    catch {
        $failures += 1
        LogErrorAndConsole "Exception: $_"
    }
}
$programs = @{
    "Adobe Acrobat"                = "Acrobat.exe"
    "[Adobe Photoshop]"            = "photoshop.exe"
    "[Adobe Illustrator]"          = "illustrator.exe"
    "Adobe Creative Cloud"         = "Creative Cloud.exe"
    "Adobe Substance 3D Painter"   = "Adobe Substance 3D Painter.exe"
    "Firefox Private Browsing"     = "private_browsing.exe"
    "Firefox"                      = "firefox.exe"
    "Google Chrome"                = "chrome.exe"
    "Microsoft Edge"               = "msedge.exe"
    "Notepad++"                    = "notepad++.exe"
    "Parallels Client"             = "APPServerClient.exe"
    "Remote Desktop"               = "msrdcw.exe"
    "TeamViewer"                   = "TeamViewer.exe"
    "[Royal TS]"                   = "royalts.exe"
    "Elgato StreamDeck"            = "StreamDeck.exe"
    "[Visual Studio]"              = "devenv.exe"
    "Visual Studio Code"           = "code.exe"
    "Camtasia Studio"              = "CamtasiaStudio.exe"
    "Camtasia Recorder"            = "CamtasiaRecorder.exe"
    "Jabra Direct"                 = "jabra-direct.exe"
    "7-Zip File Manager"           = "7zFM.exe"
    "Access"                       = "MSACCESS.EXE"
    "Excel"                        = "EXCEL.EXE"
    "OneDrive"                     = "onedrive.exe"
    "OneNote"                      = "ONENOTE.EXE"
    "Outlook"                      = "OUTLOOK.EXE"
    "PowerPoint"                   = "POWERPNT.EXE"
    "Project"                      = "WINPROJ.EXE"
    "Publisher"                    = "MSPUB.EXE"
    "Visio"                        = "VISIO.EXE"
    "Word"                         = "WINWORD.exe"
    "[PowerShell 7]"               = "pwsh.exe"
    "SQL Server Management Studio" = "ssms.exe"
    "Azure Data Studio"            = "azuredatastudio.exe"
    "Zoom"                         = "zoom.exe"
    "Skype for Business"           = "Skype.exe"
    "VLC Player"                   = "vlc.exe"   
    "Cisco Jabber"                 = "CiscoJabber.exe"
    "Microsoft Teams"              = "msteams.exe"
    "PuTTY"                        = "putty.exe"
    "wordpad"                      = "WORDPAD.EXE"
    "[AutoCAD]"                    = "acad.exe"
    "[CORSAIR iCUE Software]"      = "iCue.exe"
    "[Steam]"                      = "steam.exe"
    "Paint"                        = "mspaint.exe"
}

$LogFileName = "ShortcutRepairs.log";
$LogFilePath = "$env:temp\$LogFileName";

Function Log {
    param($message);
    $currenttime = Get-Date -format u;
    $outputstring = "[" + $currenttime + "] " + $message;
    $outputstring | Out-File $LogFilepath -Append;
}

Function LogAndConsole($message) {
    Write-Host $message -ForegroundColor Green
    Log $message
}

Function LogErrorAndConsole($message) {
    Write-Host $message -ForegroundColor Red
    Log $message
}

$programs.GetEnumerator() | ForEach-Object {
    $reg_path = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\$($_.Value)"
    try {
$apppath = $null
$target = $null
        try { $apppath = Get-ItemPropertyValue $reg_path -Name "Path" -ErrorAction SilentlyContinue } catch {}
if ($apppath -ne $null)
{
$target = $apppath + "\" + $_.Value
}
else
{
try { $target = Get-ItemPropertyValue $reg_path -Name "(default)" -ErrorAction SilentlyContinue } catch {}
}
        if ($target -ne $null) {
                LogAndConsole ("Recreating shortcut for {0} in Start Menu" -f $_.Key)
$target = $target.Trim("`"")
                $shortcut_path = "$env:PROGRAMDATA\Microsoft\Windows\Start Menu\Programs\$($_.Key).lnk"
                $description = $_.Key
                $workingdirectory = (Get-ChildItem $target).DirectoryName
                $WshShell = New-Object -ComObject WScript.Shell
                $Shortcut = $WshShell.CreateShortcut($shortcut_path)
                $Shortcut.TargetPath = $target
                $Shortcut.Description = $description
                $shortcut.WorkingDirectory = $workingdirectory
                $Shortcut.Save()
                $success += 1
        }
    }
    catch {
        $failures += 1
        LogErrorAndConsole "Exception: $_"
    }
}

Alternative method

Create a Folder that includes all the shortcuts you want to restore to the startmenu and create a Install.ps1 script – The script will only copy the shortcuts where the application is actually installed – so you can include all shortcuts in the folder even though they might not be installed on all computers.

$StartMenuFolder = "$env:ProgramData\Microsoft\Windows\Start Menu\Programs"
$ShortCuts = Get-ChildItem -Filter "*.lnk"
$ShortCuts | % {
    If(test-path("$StartMenuFolder\$($_.name)")){
        "$($_.name) already exist in start menu"
    }
    else {
        "$($_.name) not found in start menu - checking if program pointed to by shortcut exist"
        $sh = New-Object -ComObject WScript.Shell
        if(Test-Path($sh.CreateShortcut($_.FullName).TargetPath)){
            "Program exist - copying $($_.Name) into start menu folder"
            Copy-Item -Path $_.FullName -Destination $StartMenuFolder -Force
        }
        else {
            "Did not find $($sh.CreateShortcut($_.FullName).TargetPath) - will not copy $($_.name)"
        }
    }
}

Should look something similar to this.

If you have multiple languages installed on your clients in your tenant it might be wise to change the properties of each shortcut to use system variable instead. Change to %programfiles% or %programfiles(x86)% depending on where it is.

Create a detectionscript – this might need to be edited, depending on what applications you want to verify have shortcuts.

$StartMenuFolder = "$env:ProgramData\Microsoft\Windows\Start Menu\Programs"
$Count = (Get-ChildItem $StartMenuFolder | ? Name -match "Word|Outlook|Powerpoint|Edge").count
If($count -ge 4){"Installed"}

Create the package and uppload to Intune with install command:

powershell.exe -noprofile -executionpolicy bypass -file .\Install.ps1

Troubleshooting ASR Rules on the client

If the shortcuts keep disappering you can use the following code to check what ASR rules are still in block mode by running this script in PowerShell in Administrator mode. It is the Win32 API calls from Office macros that is the cause of this issue that you need to check for.

White = Off
Red = Block
Yellow = Audit
Orange = Warn

$MPPref = Get-MpPreference -ErrorAction SilentlyContinue
$AttackSurfaceIDs = $MPPref | Select-Object -ExpandProperty AttackSurfaceReductionRules_Ids
$AttackSurfaceActions = $MPPref | Select-Object -ExpandProperty AttackSurfaceReductionRules_Actions
$i = 0

foreach($Rule in $AttackSurfaceIDs){

$Color = Switch($AttackSurfaceActions[$i])
{
0 {"White"}
1 {"Red"}
2 {"Yellow"}
6 {"Orange"}
}

$RuleName = Switch($Rule)
{
56a863a9-875e-4185-98a7-b882c64b5ce5 {"Block abuse of exploited vulnerable signed drivers"}
7674ba52-37eb-4a4f-a9a1-f0f9a1619a2c {"Block Adobe Reader from creating child processes"}
d4f940ab-401b-4efc-aadc-ad5f3c50688a {"Block all Office applications from creating child processes"}
9e6c4e1f-7d60-472f-ba1a-a39ef669e4b2 {"Block credential stealing from the Windows local security authority subsystem (lsass.exe)"}
be9ba2d9-53ea-4cdc-84e5-9b1eeee46550 {"Block executable content from email client and webmail"}
01443614-cd74-433a-b99e-2ecdc07bfc25 {"Block executable files from running unless they meet a prevalence, age, or trusted list criterion"}
5beb7efe-fd9a-4556-801d-275e5ffc04cc {"Block execution of potentially obfuscated scripts"}
d3e037e1-3eb8-44c8-a917-57927947596d {"Block JavaScript or VBScript from launching downloaded executable content"}
3b576869-a4ec-4529-8536-b80a7769e899 {"Block Office applications from creating executable content"}
75668c1f-73b5-4cf0-bb93-3ecf5cb7cc84 {"Block Office applications from injecting code into other processes"}
26190899-1602-49e8-8b27-eb1d0a1ce869 {"Block Office communication application from creating child processes"}
e6db77e5-3df2-4cf1-b95a-636979351e5b {"Block persistence through WMI event subscription - File and folder exclusions not supported."}
d1e49aac-8f56-4280-b9ba-993a6d77406c {"Block process creations originating from PSExec and WMI commands"}
b2b3f03d-6a65-4f7b-a9c7-1c7ef74a9ba4 {"Block untrusted and unsigned processes that run from USB"}
92e97fa1-2edf-4476-bdd6-9dd0b4dddc7b {"Block Win32 API calls from Office macros"}
c1db55ab-c21a-4637-bb3f-a12568109d35 {"Use advanced protection against ransomware"}
}

Write-Host $RuleName -ForegroundColor $Color
$i++
}

Output similar to this: