Tim D'Annecy

Azure

#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...

#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:

Screenshot of Azure Storage account SFTP preview, Add Local User option, Username + Authentication tab

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...

#Azure #Powershell

If you have a ton of compute and storage resources in your Azure environment, it can be difficult to tell which managed disks are orphaned or not mounted to a virtual machine. This Microsoft Doc [A] has the AZ CLI command, but it's buried in a larger task to delete the found objects. This Docs page also doesn't have the Powershell equivalent and I prefer to use Powershell.

To find these disks, run one of these commands in a Cloud Shell or other Azure connected terminal:

AZ CLI:

az disk list --query '[?managedBy==`null`].[id]' -o tsv

Powershell:

Get-AzDisk | Where-Object {$_.ManagedBy -eq $null}

Discuss...

#Windows #Azure #AzureAD

If you've deployed an Azure VM and did not enable the “Login with AAD credentials”, option, you can enable sign in using Azure Active Directory credentials later using Cloud Shell with this command in Azure CLI:

az vm extension set \
--publisher Microsoft.Azure.ActiveDirectory \
--name AADLoginForWindows \
--resource-group ResourceGroup \
--vm-name VMName

After running that command, you'll need to add an entry to the local group to allow interactive sign in using RDP. The extension doesn't add this permission and you will need to do it manually, running this command in a remote Powershell:

net localgroup "remote desktop users" /add "AzureAD\user@domain.com"

You will also need to add 2 lines the RDP file downloaded from the “Connect” tab so that you can connect without issues:

enablecredsspsupport:i:0
authentication level:i:2

After connecting to the VM using RDP, you will also need to disable network-level authentication from Control Panel.

In the background, the extension will change the Join Type of the VM to “Azure AD Joined” and your Devices blade will update with that information after a couple of minutes.

No need to re-create the VM.

Just putting this here for my notes.

Discuss...

#Windows #Azure #EndpointIntune

The Freshservice Discovery Agent helps you keep track of your assets by sending details (and updates) about the machine it is installed on. You can use Microsoft Endpoint/Intune to deploy the Discovery Agent in all the computers in your tenant.

Download Discovery Agent

  1. In Freshservice, go to Admin –> Discovery Admin -> Discovery

  2. In the Download Agent section, choose Windows. Click the Download Agent button. Download Agent > Windows

Download Microsoft Win32 Content Prep Tool

  1. Go to the Microsoft Win32 Content Prep Tool Github page. Click on IntuneWinAppUtil.exe. Microsoft Win32 Content Prep Tool Github page

  2. Click the Download button. IntuneWinAppUtil.exe Download button

Creating the IntuneApp

  1. Open Windows Terminal or PowerShell as an Admin. Navigate to the downloaded exe file using cd Windows Terminal as Admin

  2. Type the command .\IntuneWinAppUtil.exe and press Enter.

  3. Fill in the information requested by the packager:

    • Please specify the source folder: Type . to use the current directory. I downloaded my Freshservice msi in a folder on the same level, so my entry would be ../FS

    • Please specify the setup file: Enter the location of the Freshservice Discovery Agent msi file. Mine is ../FS/fs-windows-agent-2.7.0.msi

    • Please specify the output folder: Type . to use the current directory. I put mine back in ../FS

    • Do you want to specify catalog folder (Y/N)? Just type n to continue.

  4. After entering that information, the package will be built in the location you specified. Microsoft Terminal INFO Done!!!

Create deployment package in Microsoft Endpoint/Intune

  1. Navigate to the Microsoft Endpoint main page.

  2. Click on the Apps blade on the right-side menu. Endpoint Apps

  3. Click All apps in the menu and then click on the Add button. Endpoint > All apps

  4. Choose Windows app (Win32) in the dropdown menu for App type and then click the Select button to continue. Select app type

  5. In the first page of the wizard, click the link Select app package file. Find the .intunewin package and click the OK button to begin uploading. Choose App package file

  6. Back on the “Add information” tab, put in the required information and click the Next button to continue. App information

  7. On the “Program” tab, don't change any of the default options. Click the Next button. Program

  8. On the “Requirements” tab, change the two required options. Click the Next button.

    • For “Operating system architecture”, select both 32 bit and 64 bit.

    • For “Minimum operating system”, select the lowest value. In this case, it's Windows 10 1607.

Requirements

  1. On the “Detection rule” tab, change the “Rules format” dropdown to “Manually configure detection rules.” Click the link to Add a new rule. In the popup pane, change the “Rule type” to “MSI” and the MSI product code should automatically generate the correct number. Click the OK button to continue and then Next. Detection rule

  2. On the “Dependencies” tab, just leave default and click the Next button. Dependencies

  3. On the “Assignments” tab, select the groups in the Required category on which you want the Freshservice Discovery tool to be installed. Click the Next button. Assignments

  4. On the “Review + create” tab, make sure the options look correct and then click the Create button. Review + create

  5. Remain on the page for a few minutes while the package uploads. When it's finished uploading and processing, the assignments will be populated and computers in the group will begin to receive the deployment. Uploading app

More information here: