Tim D'Annecy

tdannecy@gmail.com Header image

#Powershell #Windows

Recently, I was trying to run a Powershell command to retrieve the list of Local Admin accounts on a domain-joined machine.

I ran this command in Powershell 5.1 and 7.2.5:

Get-LocalGroupMember -Group Administrators

I received an error that something was wrong with the command.

Get-LocalGroupMember : Failed to compare two elements in the array. At line:1 char:1

A SuperUser post [A] suggested that the error is caused by invalid admin accounts that are not cleaned up during domain join or AAD join. The post suggests running the following Powershell command to remove the invalid admin accounts:

# Clean-AdministratorGroup.ps1
# https://superuser.com/a/1481036

function Clean-AdministratorGroup {
    $administrators = @(
        ([ADSI]"WinNT://./Administrators").psbase.Invoke('Members') |
        ForEach-Object { 
            $_.GetType().InvokeMember('AdsPath', 'GetProperty', $null, $($_), $null) 
        }
    ) -match '^WinNT';
        
    $administrators = $administrators -replace 'WinNT://', ''
        
    $administrators | ForEach-Object {   
        if ($_ -like "$env:COMPUTERNAME/*" -or $_ -like "AzureAd/*") {
            continue;
        }
        Remove-LocalGroupMember -group 'Administrators' -member $_
    }
}
Clean-AdministratorGroup

Footer image

Discuss...

#Azure #Exchange

A company I work with requested a deployment of RedCap, a web application for building and managing online surveys and databases, into their Azure environment.

Currently, the official RedCap documentation is incomplete and has inaccurate instructions on some of the Azure steps.

While I got this rolled out for the company, I decided to take detailed notes to help others in deploying this in Azure. This process is detailing my environment, running REDCap 12.5.4, PHP 7.4.28 (Windows OS), MySQL 5.7.32

Deployment

To start, you'll need the following credentials:

  • Global Administrator access to your Azure tenant or the ability to create AppService Plans and roll out Templates
  • Exchange Administrator
  • Admin access to your public DNS provider
  • RedCap Community site access

Begin by opening your browser and navigating to the official RedCap GitHub fork for the Azure Template: https://github.com/vanderbilt-redcap/redcap-azure

Click on the link in the README page with the text “Deploy with your SMTP Relay” to open the Azure Template page and begin deployment:

Screenshot of RedCap Azure Template on Github

  • Subscription: Choose your organization's subscription
  • Resource group: Create new to keep things organized
  • Region: Choose the region closest to the usage location
  • Site Name: Something with “Redcap” to keep it organized
  • Administrator Login: Keep the default of redcap_app
  • Administrator Login Password: Set to a strong password
  • Redcap Community Username and Password: Copy and paste this from your welcome email from RedCap after purchasing a license. If this information is not correct, the Template deployment will fail because it won't be able to download the RedCap installation.
  • Redcap App Zip Version: latest
  • From Email Address: Create an Shared Mailbox in Exchange and put it here like redcap@domain.com. Login to the Microsoft Admin center and reset the password for that RedCap Shared Mailbox as a User object.
  • Smtp FQDN: smtp.office365.com
  • Smtp User and password: redcap@domain.com
  • Smtp Port: 587
  • Sku Name: S1
  • Sku Capacity: 1
  • Database Sku Size MB: 5120
  • Database For My Sql Tier: General Purpose
  • Database For My Sql Family: Gen5
  • Database For My Sql Cores: 2
  • Mysql Version: 5.7
  • Storage Type: Standard_LRS
  • Storage Container Name: redcap
  • Repo URL: https://github.com/vanderbilt-redcap/redcap-azure.git
  • Branch: master

Screenshot of Azure Template deployment for RedCap

You can adjust the size of your AppService after deployment, so leave the Sku options default.

After inputting your data, deploy it and wait about 30 minutes for everything to provision and install.

I had some issues with the deployment, specifically the clean up tasks at the end. These can be ignored. You should have a working install if the following items were created successfully:

  • AppService Plan
  • AppService
  • Azure Database for MySQL single server
  • Storage Account

Screenshot of Azure Portal showing the RedCap Resource Group after template deployment

After deploying, there are quite a few things you'll need to fix before rollout.

You should be able to open the newly deployed RedCap app in the Azure Portal by navigating to your Resource Group and clicking on the RedCap AppService. On the Overview blade, click on the “Browse” button:

Screenshot of Azure App Service, Overview blade

You should be able to open the app with no authentication.

Click on the Control Center button at the top of the page and click on Configuration Check on the left pane to begin working through the many issues displayed on this page.

Fixing cron process

For some reason, the scheduled tasks performed by the cron process are not running as expected after deployment. This impacts regular server tasks like database cleanup and sending emails.

To work around this issue, you'll need to create a manual job in the App Service that forces the process to start. This manual job has two files: a PowerShell script that calls PHP and cron, and a cron file that sets a schedule for auto-running the app every minute. Since RedCap was created using an Azure Template, we won't be able to use the built in portal GUI since Azure wants to keep the git repository in sync that we used in the Template. We will need to create this process manually, but the task is configurable inside the Azure Portal after creation.

To start, open the Azure App Service page for your newly deployed app and navigate to the “Advanced Tools” blade. This opens a tool called Kudu.

From the Kudu dashboard, navigate to Debug console > PowerShell.

Screenshot of Kudu in Azure, Debug console, PowerShell

In this browser, navigate to /site/wwwroot and click on the Plus icon and select “New folder”.

Create all folders until you have the following structure: C:\home\site\wwwroot\App_Data\jobs\triggered\Start-CronService

Screenshot of Kudu in Azure, Debug console, new folder option

Create a file in that location and name it Start-CronService.ps1. Click on the pencil/edit icon and paste in the following PowerShell command:

# Start-CronService.ps1
# Tim D'Annecy - 2022-08-04
# Starts the cron PHP service
# Should be deployed to C:\home\site\wwwroot\App_Data\jobs\triggered\Start-CronService\Start-CronService.ps1
# Had to hard code URIs for some reason... Working with RedCap 12.5.1

function Start-CronService {
    $cronFile = 'C:\home\site\wwwroot\cron.php'
    $phpExe = 'C:\Program Files (x86)\PHP\v7.4\php.exe'

    Start-Process -NoNewWindow -FilePath $phpExe -ArgumentList $cronFile
}
Start-CronService

In that same folder, create a file titled settings.job and paste in the following code:

{
  "schedule": "0 */1 * * * *",
}

After creating these files, you should have the following items in your folder at C:\home\site\wwwroot\App_Data\jobs\triggered\Start-CronService:

Screenshot of Kudu in Azure, Debug console, PowerShell, File view

After saving, restart your AppService from the “Overview” blade. You will see this task appear from the AppService page under the WebJobs blade:

Screenshot of Azure portal, AppService page, WebJobs blade

You can check the status by viewing logs logs and stop/start the process, as needed.

Subdomain and HTTPS connection

At this point, your app is reachable over HTTP with the long name of blah.azurewebsites.com. It's better to use a subdomain like redcap.domain.com and to force users to access the site over HTTPS.

To accomplish both of these, you'll need to make a few changes in your DNS hosting provider, in the Azure environment, and inside the RedCap AppService.

To begin, navigate to the Azure Portal and open up your AppService for RedCap. Click on the “Custom domains” blade and change HTTPS Only to On. After this is enabled, you can add your custom subdomain.

Click the “Add custom domain” button and fill in the subdomain that you want to use. For this example, I'm using redcap.domain.com

After opening the “Add custom domain” configuration pane, open a tab for your DNS provider. The company I'm working with uses Oracle DynDNS.

From the custom domain configuration pane in Azure, you'll copy and paste the following values into your DNS provider:

  1. From “Custom Domain Verification ID” in Azure into a TXT record in DNS for asuid.redcap.domain.com.
  2. From CNAME in Azure into a CNAME record in DNS for redcap.domain.com

Screenshot of Azure AppService DNS settings and Oracle DynDNS

Once these are in place, you'll create a certificate in Azure to secure the web connection for HTTPS traffic.

Open the “Certificates (preview)” blade. Click “Add certificate”. Set the Source to “Create App Service Managed Certificate” and type in your subdomain from earlier (redcap.domain.com). The “Certificate friendly name” can be set to something more readable, if you like. Change the SSL binding to a “SNI SSL” and deploy it:

Screenshot of Azure AppService Certificates

Once this certificate is deployed, switch back to the “Custom Domains” blade. You should see your subdomain populated as “Secure”. You should be able to load your site over an HTTPS connection and your certificate will be valid. You may need to wait a little bit for DNS and the certificate to propogate.

After making this change in Azure, navigate back to your RedCap instance and open Control Center > General Configuration and change the “REDCap base URL” to your subdomain (e.g. https://redcap.domain.com).

Screenshot of RedCap General Configuration page

Azure AD single-sign on

This piece is very finicky. If you make an error in setting the SSO settings inside Redcap, you can get locked out of your instance. If this happens, you can use a SQL query on your to get back in:

UPDATE `redcap_config` SET `value`='none' WHERE `field_name`='auth_meth_global' LIMIT 1

Navigate back to your AppService for RedCap and click on the “Authentication” blade. Click “Add an identity provider”.

In the wizard, on the Basics tab, change the Identity Provider to Microsoft and change the “App registration type” to Create new app registration. Give it a name like “RedCap”. Change the “Supported account types” to Any Azure AD directory - Multi-tenant. Leave the other fields as default.

Azure AppService Authentication Add an identity provider, Basics tab

On the Permissions tab, keep the default value for User.Read:

Azure AppService Authentication Add an identity provider, Permissions tab

After adding the identity provider, return to the Authentication page. Click on the Pencil icon next to your new Authentication provider.

On the “Edit identity provider” page, copy the Application (client) ID.

Open your RedCap instance and navigate to Control Center > Security & Authentication and change the “Authentication Method” to Azure AD OAuth2. Scroll down the page and paste this Application ID into the “Azure AD API Client ID” field:

Screenshot of RedCap Azure AD OAuth2 and Azure AppService identity provider configuration

Click on Client secret value. It will redirect you to the AppService Configuration blade. Copy the value of the MICROSOFT_PROVIDER_AUTHENTICATION_SECRET into the RedCap field for Azure AD API Client Secret:

Screenshot of RedCap Azure AD OAuth2 and Azure AppService identity provider configuration

Set your RedCap@domain.com account or yourself as a Primary and/or Secondary Admin and click “Save”.

You might need to save this value twice, RedCap may reload the page without saving the values. After saving, you will need to set some values in Azure AD to handle post-login behavior.

Navigate to the Azure AD portal

Navigate to the “App registrations” blade and click on the RedCap application you named earlier. Open the “Authentication” blade.

Under the “Web” section, you'll need to add the following two URLs to your instance for the URL callback: * https://redcap.domain.com/.auth/login/aad/callback * https://redcapFULLNAME.azurewebsites.net/.auth/login/aad/callback

Under “Select the tokens you would like...” to be set to ID tokens (used for implicit and hybrid flows)

Screenshot of Azure AD App Registration for Redcap, Authentication blade

After that, navigate to the “API permissions” blade and click “Add a permission”. Select Microsoft Graph > Delegated permissions. Add the following items and save :

  • OpenID permissions
    • email – View users' email address
    • openid – Sign users in
    • profile – View users' basic profile
    • User.Read – Sign in and read user profile

Screenshot of Azure Registered Apps, API permissions page

After making these changes, try accessing your domain at https://redcap.domain.com.

If you can successfully login, you'll get this error on the page Control Center > Configuration Check:

Screenshot of RedCap, site_admin error

“siteadmin” user should not be an Administrator! It appears that the user “siteadmin” is an Administrator. However, this user account should ONLY ever be an Administrator when the system-level authentication method is set to None (Public), otherwise this user should NOT be an Administrator. You should go to the “Administrators & Acct Managers' page and remove them as an Administrator immediately, otherwise users accessing public REDCap projects may gain access to things that only REDCap Administrators should be accessing.

The instructions in this error are not accurate to the current version of RedCap. To make this error disappear, navigate to the Control Center > Administrator Privileges and uncheck all of the permissions for the user:

Screenshot of RedCap, user permissions

After that's cleaned up, you can suspend the account on the Control Center > Browse Users > View list by criteria page. Click the “Display user list” button and select the site_admin account. Check the box next to the account and suspend it using the button on the bottom:

Screenshot of Redcap, Browse Users

Do not delete this user. On a reboot of the AppService, RedCap will check to make sure this user is still present and show a database error if it's missing.

Cleaning up

After you've made these changes, you can delete some items out of the Configuration page in the AppService blade, as recommended by RedCap documentation, but there's no real reason to do so.

MySQL database changes

RedCap will give you a warning about the default MySQL database configuration:

RedCap MySQL warning message

Your database configuration settings do not appear to be optimal. For better database performance and stability, consider making the changes below to your database configuration settings in your my.cnf (Linux/Unix) or my.ini (Windows) configuration file. TIP: Remember to restart the MySQL service after making any edits to the configuration, otherwise they won't take effect.

Navigate to the Azure Portal and open up the SQL object that was created with the Template deployment. Navigate to the Query Performance Insight blade. From here, click on the top banner:

Screenshot of Azure MySQL Query Performance Insite message

Change the following options and save: * query_store_capture_mode = ALL * query_store_capture_utility_queries = YES

Screenshot of Azure MySQL server parameters

Over time, Azure should make recommendations to adjust your SQL configuration to be more efficient.

User-uploaded document store

After first install and logging in with an admin account, RedCap will give an error about the user-uploaded documents:

RedCap user-uploaded documents error message

Directory that stores user-uploaded documents is exposed to the web: It is HIGHLY recommended that you change your location where user-uploaded files are stored. Currently, they are being stored in REDCap's “edocs” directory, which is the default location and is completely accessible to the web. Although it is extremely unlikely that anyone could successfully retrieve a file from that location on the server via the web, it is still a potential security risk, especially if the documents contain sensitive information. It is recommend that you go to the File Upload Settings page in the Control Center and set a new path for your user-uploaded documents (i.e. “Enable alternate internal storage of uploaded files rather than default 'edocs' folder”), and set it to a path on your web server that is NOT accessible from the web. Once you have changed that value, go to the 'edocs' directory and copy all existing files in that folder to the new location you just set.

You can fix this by navigating to the Control Center and opening the “File Upload Settings” page. From here, change the dropdown to Microsoft Azure Blob Storage and scroll down to the section with the same title.

Open another browser tab and navigate back to your Resource Group that you setup in Azure. Open the Storage account for the app and copy and paste the Storage account name and the blob container name:

Screenshot of Azure Storage account and RedCap File Upload Settings pages

In the Azure tab, open the Access keys blade and copy and paste the Key from Key1 in the RedCap configuration page:

Screenshot of Azure Storage account and RedCap File Upload Settings pages

Save the changes at the bottom of the page, then confirm this worked by opening the Control Center and clicking on Configuration Check.

SMTP settings

The company I'm working with is using Exchange Online for its email service. To make things easy with Redcap, I created a Shared Mailbox of “RedCap@domain.com” and set a password on the Microsoft 365 Admin dashboard.

After a fresh install of RedCap 12.5.4, the SMTP settings I set in the Template did not work:

RedCap SMTP email error message

REDCap is not able to send emails – CRITICAL: It appears that your SMTP configuration (email-sending functionality) is either not set up or not configured correctly on the web server. It is HIGHLY recommended that you configure your email/SMTP server correctly in your web server's PHP.INI configuration file or else emails will not be able to be sent out from REDCap. REDCap requires email-sending capabilities for many vital application functions. For more details on configuring email-sending capabilities on your web server, visit PHP's mail configuration page.

To fix this, you'll need to open Control Center > General Settings. Scroll down to the “Configuration for Outgoing Emails” section and set the Universal FROM Email address as your redcap@domain.com email. Also set this same email under Other system settings under Email Address of REDCap Administrator.

After you update the config, you'll also need to update the version of SendMail. The one bundled with RedCap 12.5.4 does not work with TLS 1.2 and needs to be updated. To do this, we'll push the files to the AppService using FTPS.

Start by opening the GitHub page for Sendmail with TLS 1.2: https://github.com/sendmail-tls1-2/main . Download the Sendmail_v33_TLS1_2.zip file and unzip the files on your computer.

Edit the file settings.ini and put in the SMTP settings you set earlier during the deployment step, be sure to change these values to your environment:

  • smtp_server=smtp.office365.com
  • smtp_port=587
  • smtp_ssl=auto
  • auth_username=redcap@domain.com
  • auth_password=XXX
  • force_sender=redcap@domain.com
  • hostname=domain.dom

After making the changes to the file, save and close it. Now, you'll upload the patched Sendmail with the changed .ini file into your AppService using FTPS.

Navigate to the Azure Portal and open your RedCap AppService. Click on the “Deployment center” blade. Click on the “FTPS credentials” tab.

Download FileZilla or another application that can connect to an SFTP endpoint. For this example, I'll be using FileZilla.

Open the app and add a new site. Copy the connection information from the AppService FTPS credentials page into FileZilla:

Screenshot of Filzilla and Azure AppService, setting up a New Site wizard

Once you've connected to the FTPS endpoint, use the left pane to find the new Sendmail folder that you unzipped on your local computer. Navigate on the right pane to the location C:\home\site\repository\Files\sendmail. Select and right click on all of the files in the Sendmail folder in the local view and click “Upload”, overwriting all files.

Screenshot of Filzilla connected to Azure FTPS endpoint in AppService

After this is complete, restart your AppService for the patched changes to take effect.

If your organization is using Azure AD's Security Defaults, you will need to allow the redcap@domain.com account to use basic authentication to login.

To do this, open Azure AD Conditional Access policies and allow basic authentication for your redcap@domain.com account, or add an exception:

Screenshot of Azure AD Conditional Access policies, adding an exception

You will also need to create a Rule inside Exchange Online to allow emails to go through without being tagged as Spam. To do this, open the Exchange Admin center and navigate to Mail flow > Rules. Create a new rule with the following properties:

  • Name: Allow RedCap emails
  • If the message... Includes these words in the sender's address: 'redcap'
  • Do the following... Set the spam confidence level (SCL) to '-1'
  • Rule mode Enforce
  • Additional properties Sender address matches: Header

You can send a test email inside RedCap by navigating to Control Center > Email Users.

You can get logs from email delivery issues by opening the directory C:\home\site\repository\Files\sendmail inside the Kudu, or Advanced Tools, from your AppService page. The debug.log file should have any applicable email failures.

If emails are still not sending, check to make sure that the cron job is running with the WebJobs blade in your AppService.

Conclusion and caveats

After deployment and performing these cleanup tasks, you should have a fully working environment for a Microsoft shop: hosted in Azure, uses AzureAD for authentication, and Exchange Online for SMTP.

Since the official RedCap Azure template does not currently have support for automatic updating, you'll need to redeploy it each time. I haven't tried redeploying yet, but I'll make a new post when a major new version comes up.

Overall, this process has been difficult because the RedCap documentation is currently not updated and is often incomplete. Hopefully this guide can help someone else get the system online with modern hosting and authentication without headaches.

Footer image

Discuss...

#Windows #Powershell #Networking

A company I'm working with is looking to move from an OpenVPN connection to a Meraki VPN on newly installed MX hardware.

To accomplish this, I wrote a short script that can be deployed in GPO that adds the new VPN connection and uninstalls the existing OpenVPN application.

Here's the script:

# Migrate-VPN.ps1
# Adds a new Meraki VPN config and removes the existing OpenVPN GUI application.
# Tim D'Annecy 2022-08-04

Start-Transcript -Path 'C:\temp\Migrate-VPN.log'
function Add-VPN {
  $ConnectionName = 'New VPN'
  $ServerAddress = 'XXXyourhostnameXXX'
  $PresharedKey = 'XXXyourpskXXX'

  $check = Get-VpnConnection -Name $ConnectionName -AllUserConnection -ErrorAction SilentlyContinue

  if ($check) {
    Write-Host 'VPN connection named' $ConnectionName 'already exists. Exiting.'
  }
  else {
    Write-Host 'Adding VPN connection' $ConnectionName
    Add-VpnConnection `
      -Name $ConnectionName `
      -ServerAddress $ServerAddress `
      -TunnelType L2tp `
      -EncryptionLevel Optional `
      -L2tpPsk $PresharedKey `
      -AuthenticationMethod Pap `
      -RememberCredential $true `
      -AllUserConnection  $true `
      -Force `
      -WarningAction SilentlyContinue
  }
}
Add-VPN

function Remove-OpenVPN {
  if (Test-Path -Path 'C:\Program Files\OpenVPN') {
    Write-Host 'OpenVPN installed. Removing...'
    (Get-WmiObject -Class Win32_Product -filter "Name LIKE 'OpenVPN%'").Uninstall() | Out-Null
  }
  else {
    Write-Host 'OpenVPN not installed. Exiting.'
  }
}
Remove-OpenVPN

Stop-Transcript

Copy and paste this script into your \\domain.com\SYSVOL\scripts folder and save it as Migrate-VPN.ps1.

Once you've done this, go into Group Policy Management and create a new GPO Object that does 3 things: * Create a folder at C:\temp * Copy the file from \\domain.com\SYSVOL\scripts\Migrate-VPN.ps1 to C:\temp\Migrate-VPN.ps1 * Run a Scheduled Task that calls Powershell to run the script every hour on the hour

With these things in place, you should see the changes trickle out to your environment as the machines check in.

Discuss...

#Windows #Powershell #Azure

I have a few companies that I work with that are using a traditional Active Directory domain environment (GPO, WSUS, etc.) but are not using an inventory tool like Intune or PDQ.

One of the biggest issues that they report is that they aren't able to get any information about live machines in their environment.

Gathering this information is a critical step in moving to cloud-based endpoint management. You won't be able to decommission a domain if there are objects that still check back in to on-prem infrastructure for management.

To work around this, I wrote a Powershell script that runs on a local computer, gathers some information about its config, then pushes it to an Azure Table. This collected data can then be exported to a .csv file and can be ingested into other tools for analytics.

Azure Storage Account and Table

Open the Azure portal and create a new Storage Account. Keep all of the defaults and step through the wizard.

Once the deployment is complete, navigate to the Storage Account and select Tables. In this view, create a table named “domaineddevices”:

Screenshot of Azure portal, viewing a Table inside a Storage Account

After creating the Table, navigate to the Access keys blade. Copy this key and paste it into the $accesskey line in the script below:

Screenshot of Azure portal, viewing the Access Keys inside a Storage Account

For better compatibility in your environment, change the Minimum TLS version to 1.0 under the Configuration blade. This will allow older versions of Windows to check in with the Table:

Screenshot of Azure portal, viewing the Configuration blade inside a Storage Account

Once this Storage Account is setup, move to the Powershell section and paste in your Key that you copied earlier.

Powershell script

I was struggling with writing to an Azure Table, specifically creating the needed encryption pieces. I found a few posts [A] [A] that had the main crypto pieces that I needed. I wrote the rest of the information gathering lines and tweaked it to successfully upload the data that the script gathered to Azure Tables.

Here's my modified Powershell script:

# Check-DomainStatus.ps1

$ScriptVersion = 20220802

Start-Transcript -Path 'C:\temp\Check-DomainStatus.log' -Append -NoClobber
$storageAccount = 'STORAGEACCOUNT' # Update these values for your environment
$accesskey = 'XXX' # Update these values for your environment
$TableName = 'domaineddevices'
$DomainName = 'XXX' # Update these values for your environment

function InsertReplaceTableEntity($TableName, $PartitionKey, $RowKey, $entity) {
    $version = "2017-04-17"
    $resource = "$tableName(PartitionKey='$PartitionKey',RowKey='$Rowkey')"
    $table_url = "https://$storageAccount.table.core.windows.net/$resource"
    $GMTTime = (Get-Date).ToUniversalTime().toString('R')
    $stringToSign = "$GMTTime`n/$storageAccount/$resource"
    $hmacsha = New-Object System.Security.Cryptography.HMACSHA256
    $hmacsha.key = [Convert]::FromBase64String($accesskey)
    $signature = $hmacsha.ComputeHash([Text.Encoding]::UTF8.GetBytes($stringToSign))
    $signature = [Convert]::ToBase64String($signature)
    $headers = @{
        'x-ms-date'    = $GMTTime
        Authorization  = "SharedKeyLite " + $storageAccount + ":" + $signature
        "x-ms-version" = $version
        Accept         = "application/json;odata=fullmetadata"
    }
    $body = $entity | ConvertTo-Json
    Invoke-RestMethod -Method PUT -Uri $table_url -Headers $headers -Body $body -ContentType application/json
}

# GPO calculation
$RegPath = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Group Policy\State\Machine\Extension-List\{00000000-0000-0000-0000-000000000000}'
$LowTime = Get-ItemProperty -path $RegPath -name "EndTimeLo"
$HighTime = Get-ItemProperty -path $RegPath -name "EndTimeHi"
$CompTime = ([long]$HighTime.EndTimeHi -shl 32) + [long] $LowTime.EndTimeLo
$GPOProcessDate = [DateTime]::FromFileTime($CompTime)

# Reduce some calls
$dsregcmd = (C:\Windows\System32\dsregcmd.exe /status)
$computerinfo = Get-ComputerInfo 
$wmiobjectw32 = Get-WmiObject -class win32_bios

$body = @{
    # Required values 
    RowKey                     = $($env:COMPUTERNAME)
    PartitionKey               = 'domaineddevices'

    # Optional values
    AzureADJoinedStatus        = ($dsregcmd | Select-String 'AzureADJoined' | Out-String).replace(' ', '').replace("`n", '').replace("`r", '')
    AdminAccountPresent     = if ((Get-LocalUser).Name -Contains 'LocalAdmin' ) { $true } else { $false }
    Domain                     = $env:USERDOMAIN
    DomainJoinStatus           = ($dsregcmd | Select-String 'DomainJoined' | Out-String).replace(' ', '').replace("`n", '').replace("`r", '')
    EnterpriseJoinedStatus     = ($dsregcmd | Select-String 'EnterpriseJoined' | Out-String).replace(' ', '').replace("`n", '').replace("`r", '')
    FortiClientVPNFilesPresent = if (Test-Path -Path 'C:\Program Files\Fortinet\FortiClient' -ErrorAction SilentlyContinue) { $true } else { $false }
    FortiClientVPNRunning      = if (Get-Process -ProcessName 'FortiTray' -ErrorAction SilentlyContinue) { $true } else { $false }
    # # GPOProcessDate             = [datetime]::FromFileTime(([Int64] ((Get-ItemProperty -Path "Registry::HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Group Policy\State\Machine\Extension-List\{00000000-0000-0000-0000-000000000000}").startTimeHi) -shl 32) -bor ((Get-ItemProperty -Path "Registry::HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Group Policy\State\Machine\Extension-List\{00000000-0000-0000-0000-000000000000}").startTimeLo)).toString()
    GPOProcessDate             = [datetime]$GPOProcessDate
    LogonServer                = $env:LOGONSERVER | Out-String
    Manufacturer               = ($wmiobjectw32).Manufacturer
    NetworkMAC                 = (Get-WmiObject win32_networkadapterconfiguration | Select-Object Description, MACaddress, IPAddress, DefaultIPGateway, DNSDomain) | Out-String
    OSBuild                    = (($computerinfo).OsHardwareAbstractionLayer | Out-String).replace(' ', '').replace("`n", '').replace("`r", '')
    OSEdition                  = (($computerinfo).WindowsProductName | Out-String).replace(' ', '').replace("`n", '').replace("`r", '')
    OSVersion                  = [int32]((($computerinfo).WindowsVersion | Out-String).replace(' ', '').replace("`n", '').replace("`r", ''))
    QuestODMAgentRunning       = if (Get-Process -ProcessName 'BinaryTree.ADM.Agent' -ErrorAction SilentlyContinue) { $true } else { $false }
    QuestODMFilesPresent       = if (Test-Path -Path 'C:\Program Files (x86)\Quest\On Demand Migration Active Directory Agent' -ErrorAction SilentlyContinue) { $true } else { $false }
    ScriptVersion              = [int32]$ScriptVersion
    SerialNumber               = ($wmiobjectw32).SerialNumber
    StorageType                = (Get-PhysicalDisk).MediaType | Out-String
    Traceroute                 = (Test-NetConnection -TraceRoute $DomainName -Hops 5 -ErrorAction SilentlyContinue) | Out-String
    Uptime                     = (New-TimeSpan -Start (Get-CimInstance -Class Win32_OperatingSystem -Property LastBootUpTime).LastBootUpTime -End (Get-Date)).ToString()
    Users                      = (Get-ChildItem -Path 'C:\Users\' | ForEach-Object {
            $size = Get-ChildItem -Path $_.FullName -Recurse -ErrorAction SilentlyContinue | Measure-Object -Property Length -Average -Sum -ErrorAction SilentlyContinue
            Write-Output $_.Name, $_.LastWriteTime.ToString("yyyy-MM-dd"), "$([math]::round($size.sum/1GB)) GB", '---' }) | Out-String
    WindowsVPNManualStatus     = (Get-VpnConnection -ErrorAction SilentlyContinue).Name | Out-String
    WindowsVPNStatus           = (Get-VpnConnection -AllUserConnection -ErrorAction SilentlyContinue).Name | Out-String
}

Write-Host 'Creating new or updating table entity'
InsertReplaceTableEntity -TableName $TableName -entity $body -RowKey $body.RowKey -PartitionKey $body.PartitionKey

Write-Host 'Outputting all values for log:'
Write-Host $body 
Stop-Transcript

Save that script to somewhere like SYSVOL.

Group Policy Object

After saving the file to the domain controller, create a GPO with the following items:

Computer Configuration > Preferences > Windows Settings > File

General tab:

Screenshot of Group Policy Management Editor File wizard

  • Source file(s): \\domain.local\SYSVOL\domain.local\scripts\Check-DomainStatus.ps1

  • Destination FIle: C:\temp\Check-DomainStatus.ps1

Computer Configuration > Control Panel Settings > Scheduled Tasks

General tab:

Screenshot of Group Policy Management Editor Scheduled Tasks wizard

  • Action: Replace

  • Name: Check-DomainStatus

  • When running the task, use the following user account: NT AUTHORITY\System

  • Run whether user is logged on or not

  • Run with highest privileges

  • Configure for: Windows Vista or Windows Server 2008

Triggers tab:

Screenshot of Group Policy Management Editor Scheduled Tasks wizard

  • New > Begin the task: On a schedule

  • Daily, Recur every: 1 days

  • Repeat task every: 1 hour for a duration of: 1 day

  • Enabled

Actions tab:

  • New > Action > “Start a program”

  • Program/script: powershell.exe

  • Add arguments(optional): -NoProfile -ExecutionPolicy Bypass -File "c:\temp\Check-DomainStatus.ps1"

Conditions tab:

Screenshot of Group Policy Management Editor Scheduled Tasks wizard Conditions tab

  • All options unchecked.

Settings tab:

Screenshot of Group Policy Management Editor Scheduled Tasks wizard Settings tab

  • Allow task to be run on demand

  • Run task as soon as possible after a scheduled start is missed

  • Stop the task if it runs longer than 1 hour

  • If the running task does not end when requested, force it to stop

  • If the task is already running, then the following rule applies: Do not start a new instance

Once deployed, the task will be available on the local machine in Task Scheduler and can be started immediately:

Screenshot of Task Scheduler MMC

Azure Storage Explorer

After deploying the script, you can use the Azure Storage Explorer app to view and export the data as it arrives:

Screenshot of Azure Storage Explorer opening a Table

Discuss...

#Windows #Powershell

One of the companies I work with just added Radius authentication to an SSID on Meraki APs. To do this, they setup an NPAS role on the domain controller and connected it with the Meraki config.

Most users are working. Some are experiencing issues logging into the network, receiving the message “Can't connect to this network” when they try to authenticate:

Screenshot of Windows 10 wifi network message saying 'Can't connect to this network'

I troubleshooted everything I could think of: local machine, domain trust, user password, Radius/LDAP settings, Meraki authentication settings, etc. and found that the issue was the msNPAllowDialin attribute when it was set to “False”.

You can view this attribute by opening a user in ADUC when you're remoted onto a domain controller. Make sure you have the option checked under View > Advanced Features. Open the Dial-in tab and check the Network Access Permission field:

Screenshot of Active Directory Users and Computers Dial-In tab

Now that I know the root cause, I wanted to find how many active users were affected. To get a list of all the users, I ran this Powershell command:

Import-Module ActiveDirectory
Get-ADUser -Filter "enabled -eq 'true'" -Properties Name,msNPAllowDialin | Select-Object Name,msNPAllowDialin | Sort-Object -Property Name | Export-Csv -Path .\out.csv -NoTypeInformation

I opened the CSV in Excel and was able to sort by “False” and find the users that had the attribute.

From what I understand, the msNPAllowDialin attribute should be “null” to allow NPAS to handle the authentication.

With this in mind, I was able to clean up the environment by running this Powershell command:

Get-ADUser -Filter "enabled -eq 'true'" -Properties Name,msNPAllowDialin | Where-Object {$_.contains('msNPAllowDialin') -eq $true} | Set-ADUser -Clear msNPAllowDialin

Alternatively, as described by this Microsoft Doc [A] I could have checked the option inside the NPAS settings for “Ignore user account dial-in properties”.

Discuss...

#Windows

I needed to repair an Office 365 installation on a PC that had UAC turned off. I ran into problems:

  • I couldn't switch users on a screenshare to open the GUI. So I needed to use the Command Prompt.

  • I couldn't remember the command to repair office in CMD.

I started looking.

Once I located the command on this page [A], I found the arguments I needed, but the path to the ClickToRun.exe file was pointing to an older version of Office.

Here's the updated command for repairing 64-bit Office 365 applications from the command line:

'C:\Program Files\Common Files\microsoft shared\ClickToRun\OfficeClickToRun.exe' scenario=Repair

Discuss...

#Powershell #Exchange

I got a request to create a Dynamic Distribution List/Group in Exchange that was automatically populated based on the users' office location.

The requestor stated that they do not want to manage any additional O365 objects. I know how to do this in Azure AD with a Dynamic Assignment, but needed to figure out how to do this in Exchange Online.

Luckily, it's pretty easy.

You'll need the Exchange Online Powershell module before running the command.

Import-Module ExchangeOnline 
Connect-ExchangeOnline
New-DynamicDistributionGroup -Name 'Raleigh Staff' -Alias 'Raleigh.Staff' -RecipientFilter "(RecipientTypeDetails -eq 'UserMailbox') -and (Office -eq 'Raleigh')"

It might take a few minutes, but after running that command, you'll see it update in the Exchange Online portal and the query will add users to the List/Group.

Discuss...

#Powershell #Windows #Networking

I have a client that is transitioning their network equipment from Fortigate to Meraki. Part of this transition is testing the Meraki Client VPN instead of the FortiClient application.

We found that that on first run, the FortiClient VPN app disables some services that are needed for the Meraki VPN connection to successfully authenticate. If users don't have Local Admin permissions, they are unable to make any changes to the services to fix the issue.

To work around this, I created a small PowerShell script that can be deployed through GPO or Intune. It stops all of the FortiClient services and processes and re-enables the services that Meraki's VPN uses. It also creates a transcript and stores the log to C:\Fix-MerakiVPN.log that you can use for troubleshooting.

Here's the script:

#Requires -Version 1
<#
.SYNOPSIS
  Closes and disables FortiClient VPN services and apps. Checks and configures Windows services to allow Meraki VPN connection.
.DESCRIPTION
  Closes and disables FortiClient VPN services and apps. Checks and configures Windows services to allow Meraki VPN connection.
.INPUTS
  None
.OUTPUTS
  Log file stored in C:\Fix-MerakiVPN.log
.NOTES
  Version:        1.0
  Author:         Tim D'Annecy
  Creation Date:  2022-06-07
  Purpose/Change: Initial script development
.EXAMPLE
  Fix-MerakiVPN.ps1 
#>

$ServicesToStop = 'FA_Scheduler'#, 'FMAPOService'
$ServicesToStart = 'PolicyAgent', 'IKEEXT'
$AppsToStop = 'FortiClient', 'FortiSettings', 'FortiSSLVPNdaemon', 'FortiTray'

function Fix-MerakiVPN {
  foreach ($App in $AppsToStop) {
    if (Get-Process -Name $App -ErrorAction SilentlyContinue) {
      Write-Host 'Application running. Stopping:' $App
      Stop-Process -Name $App -Force 
    }
    else {
      Write-Host 'OK: Application not running or not installed:' $App
    }
  }
  foreach ($service in $ServicesToStop) {
    if ((Get-Service $service -ErrorAction SilentlyContinue).status -eq 'Running') {
      Write-Host 'Service running. Stopping:' $service
      $ServicePID = (get-wmiobject win32_service | Where-Object { $_.name -eq $service }).processID
      Stop-Process $ServicePID -Force
      Set-Service $service -StartupType Disabled
    }
    else {
      Write-Host 'OK: Service not running or not installed:' $service
    }
  }
  foreach ($service in $ServicesToStart) {
    if ((Get-Service $service -ErrorAction SilentlyContinue).status -eq 'Running') {
      Write-Host 'OK: Service running:' $service
    }
    else {
      Write-Host 'Service not running. Starting:' $service
      Set-Service $service -StartupType Automatic -Status Running 
      Start-Service $service 
    }
  }
}

Start-Transcript -Path 'C:\Fix-MerakiVPN.log' -Append
Fix-MerakiVPN
Stop-Transcript

Discuss...

#Windows #Powershell

I received this error on a fresh Windows 10 install a machine provisioned by Autopilot.

I found a fix on this post [A] and wanted to paste out the Powershell command for future reference.

$WinRMClient = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WinRM\Client"
$Name = "AllowBasic"
$value = "1"
IF (!(Test-Path $WinRMClient)) {
   New-Item -Path $WinRMClient -Force | Out-Null
   New-ItemProperty -Path $WinRMClient -Name $name -Value $value -PropertyType DWORD -Force | Out-Null
} ELSE {
   New-ItemProperty -Path $WinRMClient -Name $name -Value $value -PropertyType DWORD -Force | Out-Null
}

Discuss...

#Azure

I received a request for a hosted SFTP solution for one of the clients I work with.

Currently, there are some recommended templates from Microsoft [A] that include Blob storage and a Debian container that serves up the SFTP service. While this solution will work, I'm looking for a solution that's easier to manage.

Recently in Preview, Azure now has the ability to add the SFTP endpoint and features to a Storage Account.

Here's how you do it.

Open the Azure Portal and create a new Storage Account. You should be able to add this feature to existing Storage Accounts, as long as they are StorageV2 (general purpose v2) of Block Blob.

Screenshot of Azure Dashboard creation of new Storage Account

On the Advanced tab, check the boxes for “Enable hierarchical namespace” and “Enable SFTP (preview)”:

Screenshot of the Azure Create a Storage Account wizard Advanced tab

Leave the rest of the options as defaults.

After creating the resource, navigate to the “SFTP (Preview)” blade under the Settings header. From there, click “Add local user”. Type the name for the user and check the box to use an SSH Password:

Still in the “Add local user” side menu, click on the “Container permissions” tab and add a new container that you plan to use for storage. Change the permissions to fit what kind of access you need, then set the “Home directory” to the virtual folder you want to use. If this value is not set correctly, you'll have connection/mounting issues later on. To keep things simple, I set it to the Container that I set in the top row:

Screenshot of Azure Storage account SFTP preview, Add Local User option, Container permissions tab

Copy the SFTP user password somewhere like Notepad and return to the SFTP blade.

After getting the SFTP features set up, you can connect to your container using the connection information in the “Connection string” option. Click on the Copy icon:

Screenshot of Azure Storage account SFTP preview, Connection string option

Next, get an SFTP app like Filezilla to setup the connection. Paste the “Connection string” into Filezilla's “Host” field. The username will populate from this information. Paste in the password that you saved from earlier, then click “Quickconnect”:

Screenshot of Filezilla with a successful SFTP connection to Azure Storage

And that's it!

For it to be fully featured, there are a few things that I would like to see added (the ability to use Azure AD accounts and Managed Identities). Right now, it's working great as a fully managed SFTP endpoint that you can use for a few dollars per month: It supports regular Blob features (Soft Delete, Azure Monitoring and Firewalls, etc.) and can hook into a vNet using a Service Endpoint.


Additional resources:

Discuss...

Enter your email to subscribe to updates.