Tim D'Annecy

PowerShell

#Windows #Powershell

One of the companies I'm working with has an Intune installation package for Adobe Acrobat Pro DC version 15.007.20033, but seems to have an issue with signing in on any PC that gets the deployment. Even newly imaged computers running Windows 10 21H2 get the error.

The package was created in Intune as a regular Line of Business app using a freshly generated .msi file from the Adobe Admin console under the Packages tab:

To get the application rolled out: I created a security group in Azure Active Directory named “Adobe Acrobat Pro DC users” that is used for the following tasks (not in this order):

  1. Uninstalls Adobe Reader DC (This removal is to simplify the user experience opening .pdf files, but isn't needed for functionality)

  2. Provisions an Adobe Acrobat Pro DC license using a configuration in Enterprise Applications

  3. Installs the Adobe Acrobat Pro DC .msi file

After the Adobe Acrobat Pro DC installation is complete on a user's computer and on first run, the user is prompted to login with their Adobe account. Since these users are already provisioned, it should be an easy click through. When the user hits the signin page, however, an error message appears and doesn't let the user continue:

Update required: Your browser or operating system is no longer supported. You may need to install the latest updates to your operating system. Learn more.

This seems to be a bug and users are reporting the issue on the Adobe Community forums [A]. The post notes that the issue is with an older version of the file AASIapp.exe that is causing that update error message. To work around this, they provide some steps from Adobe Support that can be used to fix the issue.

I wanted to make this deployable in Intune, so I wrote the following script:

function Invoke-AdobeAcrobatDCFix {
    $DownloadURI = 'http://prdl-download.adobe.com/Framemaker/428037A8066D4558A7EF7D7D06CB5B72/1600836995996/AASIapp.exe'
    $DownloadDestination = 'C:\temp\AASIapp.exe'
    $AppDestination = 'C:\Program Files (x86)\Common Files\Adobe\OOBE\PDApp\P7'

    Invoke-WebRequest -Uri $DownloadURI -OutFile (New-Item -Path $DownloadDestination -Force)
    Copy-Item -Path $DownloadDestination -Destination $AppDestination -Force
}

Invoke-AdobeAcrobatDCFix

To get this working in your environment, follow these steps:

  1. Copy the script snippet above and paste it into a text editor. Save it as a .ps1 file.

  2. Open https://endpoint.microsoft.com/

  3. Navigate to Devices > Scripts > Add > Windows 10 and later:

  1. Move through the wizard to upload and configure your script deployment:

    1. Basics: Name it something you'll remember and add a description.

    2. Script settings: Upload the .ps1 file you saved earlier. Leave the other options on the default “No” setting.

    3. Assignments: Select the user group that you're using for the Adobe Acrobat Pro DC app deployment. In my environment, this is the “Adobe Acrobat Pro DC users” security group.

  2. Keep the Scripts tab open for a few seconds. After the upload message pops up, the deployment will begin to sync to devices:

You can check the deployment process on the PC by looking for the C:\temp\ folder or for a newer timestamp on the file at C:\Program Files (x86)\Common Files\Adobe\OOBE\PDApp\P7\AASIapp.exe:

If the script fails, you can check the Intune application log at C:\ProgramData\Microsoft\IntuneManagementExtension\Logs\AgentExecutor.log for Powershell error messages.

If Adobe decides to stop hosting the file, this process could stop working. You might want to download that .exe file and put it in a public container in an Azure Storage account.

I'm sure this script could be shorter and the process could be more streamlined (I'm thinking editing the .msi file), but it's working for me and doesn't require too much upkeep. After assigning a user the Adobe Acrobat Pro DC users security group, after a little bit of time, the user will have a fully working Adobe Pro installation.

I hope this helps!

Discuss...

#Windows #Powershell

Here's a single-line Powershell command to delete user profiles that are older than 6 months.

I got this from a Spiceworks community post [A] and I fixed the typos.

Get-WMIObject -class Win32_UserProfile | Where-Object {(!$_.Special) -and ($_.ConvertToDateTime($_.LastUseTime) -lt (Get-Date).AddDays(-183))} | Remove-WmiObject 

This command may take a few minutes to run, but you can watch the progress as your free space expands in File Manager.

If you change the AddDays(-183) to a different number, you can change the interval for when you want to clean up. Make sure you keep the negative symbol for days in the past.

Just putting this here for my notes.

Discuss...

#Powershell #Windows #networking

I found a great tool [A] that runs netstat to get the currently listening and active ports on the local machine while matching the process IDs with the process names.

This comes in handy when trying to troubleshoot potential firewall or other access issues on a machine.

Here's the code:

$netstat = netstat -aon | Select-String -pattern "(TCP|UDP)"
$processList = Get-Process

foreach ($result in $netstat) {
   $splitArray = $result -split " "
   $procID = $splitArray[$splitArray.length - 1]
   $processName = $processList | Where-Object {$_.id -eq $procID} |    select processname
   $splitArray[$splitArray.length - 1] = $procID + " " +      $processName.processname
   $splitArray -join " "
}

Discuss...

#Powershell #Windows

A company I'm working for is decommissioning a Meraki firewall in the near future. With this piece of hardware gone, there would be no web content filtering for all users in the organization.

As a temporary workaround, I moved all of the existing URL blocks from the Meraki dashboard into a Group Policy Object that targets Chrome and Edge. When creating the policy, the Group Policy Management MMC tool only allows you to put one entry in at a time for the URL Blocking. I would need to copy and paste hundreds of entries to get this updated.

There has to be a faster way. I read through this post [A] and it gave me some information about updating a GPO generally. I tweaked it a bit and targeted both Chrome and Edge in my environment.

Here's how I did it.

ADMX template check

Before you can continue, make sure you have the ADMX files added to the Domain Controller.

I won't cover that in this post (basically download the files and copy over to \\domain.com\SYSVOL\domain.com\Policies\PolicyDefinitions and en-US folders), but check the following guides for deployment information:

When these ADMX templates are added, go to the next step.

Create a Group Policy Object

Now, let's create a GPO policy and change one option per app (Chrome or Edge).

Open the Group Policy Management MMC. Right click on the “Group Policy Objects” container/folder and select “New”:

https://i.imgur.com/w2bwNoB.png

In the popup, give your GPO a name that you'll remember. Keep the “Source Starter GPO” set to (none).

When that's created, move to the next step to define your blocked sites.

Create the URL list file

The Chromium documentation for this Group Policy Object [A] states that putting in a simple domain name of example.com will block all http and https requests to any subdomains and the root.

I created a list of URLs in Notepad with one entry on each line, like this example:

facebook.com
twitter.com
youtube.com

This was an easy copy and paste from the Meraki Group Policies page at Network Wide > Configure > Group Policies:

Run some Powershell commands

For these Powershell commands to run, first make sure you are able to connect to a writable Domain Controller. It might be easier to remote into the server and run the commands locally.

I would also recommend appending -whatif to the end of the Set-GPRegistryValue in the command below if you're not sure you've done everything correctly.

Also make sure to change the -name of your GPO to match the one you created in the first few steps.

For adding the URL blocks in Google Chrome, use this command:

Get-Content .\URLlist.txt | foreach {
     Set-GPRegistryValue -Name 'XXXyourGPOnameXXX' -ValueName $_ -Type String -Value $_ -Key "HKLM\Software\Policies\Google\Chrome\URLBlocklist"    
}

For adding the URL blocks in Microsoft Edge, use this command:

Get-Content .\URLlist.txt | foreach {
     Set-GPRegistryValue -Name 'XXXyourGPOnameXXX' -ValueName $_ -Type String -Value $_ -Key "HKLM\Software\Policies\Microsoft\Edge\URLBlocklist"    
}

Caveats

Running these Powershell commands can take a long time if you have a lot of entries. My final version had over 100 lines and took about 15 minutes to complete.

You can verify that this worked by opening the Group Policy Management Editor again and clicking on your Policy Object. Select the “Settings” tab. Click “Show All” at the right side and scroll down to the section labelled “Block access to a list of URLs”:

If you need to add/update or remove a URL from this list later on, right click on the Policy Object and select “Edit”. Navigate to the following location:

  • Chrome: Computer Configuration > Policies > Administrative Templates > Google Chrome > Block access to a list of URLs
  • Edge: Computer Configuration > Policies > Administrative Templates > Microsoft Edge > Block access to a list of URLs

From there, you can click on the “Show” button to make changes to each line.

Discuss...

#Exchange #Powershell

I was looking for some good documentation online on how to recall emails from user inboxes.

First, import and connect to the relevant Powershell modules and environments:

Install-Module ExchangeOnlineManagement
Import-Module ExchangeOnlineManagement
Connect-IPPSSession -UserPrincipalName XXX # Change this value to your account with Global Admin or Compliance Admin permissions

Next, create a new Compliance Search by defining your scope and query. For this example, I'm going to keep it simple by targeting all Exchange content and a subject line search:

# Change the subject line to the emails' subject line in question
New-ComplianceSearch -Name "New search" -ExchangeLocation All -ContentMatchQuery '(Subject:"Spammy email subject line")' | Start-ComplianceSearch

Depending on the size of your tenant, this may take a while, maybe a few hours. You can check the status of the search by running Get-ComplianceSearch

After the search status says 'Completed', the following command to purge and delete all instances of the email from your tenant:

New-ComplianceSearchAction -SearchName "New search" -Purge -PurgeType HardDelete

This command may take a while too, depending on the size of your tenant. You can check the status of the search action with the command Get-ComplianceSearchAction

You can see the results of these actions in the Microsoft Compliance center and a full audit log of your admin actions will be available to view through the portal.

It might be a good idea to do some spot checking before you delete all of the emails. If you want to check the content of the email before you delete, you will need to add yourself to the Role “Compliance Data Administrator” in Azure AD.

After adding the Role to your account, navigate to the Purview or Compliance admin dashboard: https://compliance.microsoft.com/

Navigate to the “Content search” section and select the Compliance Search that you saved earlier. In the popup pane, click the “Review sample” button:

Screenshot of Microsoft Purview with Content search pane opened

From this view, you can click on items on the left side of the pane and view the message on the right:

Screenshot of Microsoft Purview, Content search preview samples

By taking a second look before deleting emails, this could prevent some accidental deletions and save you some headaches.

Discuss...

#Powershell #HyperV

I read a great post by Benjamin Armstrong [A] with a one-line Powershell command to list virtual machines in Hyper-V that have a missing or broken VHD disk.

This helped me do some weeding on some Hyper-V machines and clean up old/stale VMs.

get-vm | Get-VMHardDiskDrive | %{write-host $_.VMName.PadRight(40) ":: VHD Exists :: "-NoNewline; Test-Path $_.Path}

Just putting this here for my notes.

Discuss...

#Windows #Microsoft #Powershell

Exchange has the ability to limit sending permissions on Distribution Groups. Finding which Distribution Groups have user sending permissions assigned to them can be very time consuming using the portal.

To make it quicker, you can list the accounts that have the ability to send to a specific distribution group using PowerShell.

$FormatEnumerationLimit=-1 # This allows the property to be expanded in format-table

Connect-ExchangeOnline

Get-DistributionGroup -Identity XXX | Format-table -Wrap -AutoSize -property name,acceptmessagesonlyfrom # Change XXX to the Distribution Group SAM account name

Discuss...

#Powershell

I company I work for just completed an O365 tenant migration. After email had been moved to a new Exchange tenant, we noticed that users continued to use their Outlook apps on their phones. They were also continuing to chat on the old tenant's Teams and were using all of the Office apps on the web using their cached logins.

This caused a headache. Some users were in the new environment with the correct domain—others didn't notice the “@onmicrosoft.com” and were having issues with SSO apps that had been migrated.

We needed to revoke all of the cached login tokens force log out all users. This quick Powershell command did the trick:

Connect-AzureAD

$users = Get-AzureAdUser -All $true

$users | foreach {
    Revoke-AzureADUserAllRefreshToken -objectID $_.objectID
}

After this runs, all users will be required to log in again. This forced them to go into the new tenant and solved our SSO issues.

Discuss...

#Windows #Powershell

Here's a script that I'm using to roll out the Quest ODM agent on PCs in my environment that do not have access to the LAN. I used Atera Service Desk to deploy it for internet-only installation.

Agent install and hosting

This script requires that you download the Device Agent from the Quest Migration for Active Directory downloads page:

Once that's complete, you will need to upload the file to a publicly accessible file share. I used Azure Files to create a storage container and provide direct access to the file. This URI will be pointed to in the script, so you cannot use something like OneDrive or SharePoint without special configuration.

Script

Before running this script, you will need to change the following XXX values:

Function Invoke-ODM_Agent_Install {

	$InstallCheck = Get-ItemProperty -Path "HKLM:\SOFTWARE\WOW6432Node\Quest\On Demand Migration For Active Directory\ODMAD_AD" -ErrorAction SilentlyContinue
  
  If ($null -eq $InstallCheck) {
    Write-Host 'Downloading ODM agent.' 
    $QuestODMMSIURI = 'XXX' # Change to use your own Azure Files URI
    $QuestODMdest = 'C:\Temp\ODM\QuestODM.msi' 
    Invoke-WebRequest -Uri $QuestODMMSIURI -OutFile (New-Item -Path $QuestODMdest -Force)

    Write-Host 'Installing ODM agent.'
    cmd /c "msiexec.exe /I `"C:\Temp\ODM\QuestODM.msi`" /qn SERVICEURL=https://us.odmad.quest-on-demand.com/api/ADM AUTHKEY=XXX" # Change to use your own authkey from Quest
    Write-Host 'Finished installing ODM agent.' 
  }
  ElseIf ($null -ne $InstallCheck) {
    Write-Host 'ODM agent is already installed.'
  }
  Else {
    Write-Host 'ERROR!' 
  }

}

Invoke-ODM_Agent_Install

Discuss...

#Windows #Powershell #Meraki

I wrote up a quick and dirty Powershell script today that adds a split-tunnel VPN connection, asks the user for connection info, dials the connection, then configures static routes.

# Add-MerakiVPN.ps1
# Creates a split-tunnel VPN connection and adds static routes.
# Tim D'Annecy 2021-09-08

function Add-MerakiVPN {
 
    $ServerAddress = 'blahblahblah.dynamic-m.com' # Change this value to match your Meraki hostname
    $ConnectionName = 'Meraki VPN'
    $PresharedKey = 'blah' # Change this value

    Add-VpnConnection `
        -Name $ConnectionName `
        -ServerAddress $ServerAddress `
        -TunnelType L2tp `
        -EncryptionLevel Optional `
        -SplitTunneling `
        -AllUserConnection `
        -L2tpPsk $PresharedKey `
        -AuthenticationMethod Pap, MSChapv2 `
        -Force

    $StaticRoutes = @(
        '10.0.13.0/24', # Change these to match your internal subnets
        '10.0.12.0/24',
        '172.16.0.0/16'
    ) 

    try {
        rasphone.exe -d $ConnectionName
        Start-Sleep -Seconds 30
        $StaticRoutes | foreach {
            New-NetRoute -DestinationPrefix $_ -InterfaceAlias $ConnectionName
        }
    }
    catch {
        Write-Error 'There was an error adding the VPN connection'
        exit
    }
}

Add-MerakiVPN