Collect user and group SIDs and names from Active Directory

Ever found yourself looking through the Access Control List of a file/folder/share and mixed in along with the group names (hopefully not user names!) you see some SIDs? These look something like S-1-5-21-0123456789-0123456789-0123456789-0123.
These are the Security IDs of deleted groups and users. Wouldn’t it be handy to have a list of these so you could work out what it was that used to have permission, but that’s now been deleted? Yes it would, so I wrote a basic PowerShell script to collect all the SIDs and user names and store them in an XML file. It also has a field for whather the item is a user or a group, and also the date and time the item was added.
You can run the script as a regular user, but will need to AD PowerShell cmdlets installed (possibly via RSAT if on a client OS).

$XMLFile = "C:\Users\Public\Documents\UsersAndGroups.xml"
# Get users and groups from AD
$ADUsers = Get-ADUser -Filter * | Select-Object -Property Name,SID
$ADGroups = Get-ADGroup -Filter * | Select-Object -Property Name,SID
# Create an array to store AD users and groups
$UsersAndGroups = New-Object -TypeName System.Collections.ArrayList
# Add users to array
foreach($User in $ADUsers){
    $ThisUser = New-Object -TypeName System.Object
    Add-Member -InputObject $ThisUser -MemberType NoteProperty -Name SID -Value $User.SID.Value
    Add-Member -InputObject $ThisUser -MemberType NoteProperty -Name Name -Value $User.Name
    Add-Member -InputObject $ThisUser -MemberType NoteProperty -Name Type -Value "User"
    Add-Member -InputObject $ThisUser -MemberType NoteProperty -Name DateAdded -Value (Get-Date -Format s)
    $UsersAndGroups.Add($ThisUser) | Out-Null
}
# Add groups to array
foreach($Group in $ADGroups){
    $ThisGroup = New-Object -TypeName System.Object
    Add-Member -InputObject $ThisGroup -MemberType NoteProperty -Name SID -Value $Group.SID.Value
    Add-Member -InputObject $ThisGroup -MemberType NoteProperty -Name Name -Value $Group.Name
    Add-Member -InputObject $ThisGroup -MemberType NoteProperty -Name Type -Value "Group"
    Add-Member -InputObject $ThisGroup -MemberType NoteProperty -Name DateAdded -Value (Get-Date -Format s)
    $UsersAndGroups.Add($ThisGroup) | Out-Null
}
$UsersAndGroups.Count
# Get existing data if it already exists
if(Test-Path -Path $XMLFile){
    $XMLData = Import-Clixml -Path $XMLFile
    $XMLDataArrayList = New-Object -TypeName System.Collections.ArrayList
    $XMLDataArrayList.AddRange($XMLData)
    $XMLDataArrayList.Count
    # Update existing data with new SIDs
    foreach($Item in $UsersAndGroups){
        if($XMLData.SID -contains $Item.SID){
            #Write-Host $Item.SID -ForegroundColor Green
        }else{
            Write-Host $Item.SID -ForegroundColor Red
            $XMLDataArrayList.Add($Item) | Out-Null
        }
    }
    # Write updated data back to XML file
    $XMLDataArrayList | Export-Clixml -Path $XMLFile
}else{
    # Write first time data to XML file
    $UsersAndGroups | Export-Clixml -Path $XMLFile
}

To view the file, and be able to easily search it, just use:

Import-Clixml -Path "C:\Users\Public\Documents\UsersAndGroups.xml" | Out-GridView
Advertisements
Posted in PowerShell, Security, Windows | Tagged , , , , , , | Leave a comment

PowerShell Exchange mailbox move progress monitor

This is a very basic script that’ll give you a progress bar to monitor a mailbox move process. You’ll need to run it from an Exchange Management Shell PowerShell prompt.

param([string]$Identity = "")
$Stats = Get-MoveRequestStatistics -Identity $Identity
do
{
    $Stats = Get-MoveRequestStatistics -Identity $Identity
    Write-Progress -Activity "Moving $Identity" -Status ([string]$Stats.BytesTransferred+" transferred of "+[string]$Stats.TotalMailboxSize) -PercentComplete $Stats.PercentComplete
    Start-Sleep -Milliseconds 500
}
while (($Stats.PercentComplete -le 99) -and ($Stats.Status -eq "InProgress"))
do
{
    $Stats = Get-MoveRequestStatistics -Identity $Identity
    Write-Progress -Activity "Moving $Identity" -Status $Stats.Status -PercentComplete 100
    Start-Sleep -Milliseconds 500
}
while ($Stats.StatusDetail -ne "Completed")
$Stats.StatusDetail
Posted in Exchange, PowerShell | Tagged , , , , , , , | Leave a comment

Change BitLocker Recovery Password with PowerShell

When BitLocker detects certain changes to the computer it’ll trigger Recovery Mode, and prompt for the Recovery Password. Likewise, you also need the recovery password if you need to access the encrypted disk from another machine or via Windows Recovery Environment (Windows RE).

If you need to provide your users with their BitLocker recovery password, you might want to change it afterwards. It allows them to get into the disk via alternative methods and thus bypass NTFS security. This is a bad thing.

If you have BitLocker set up right, it’ll write any new recovery passwords that it generates to Active Directory. Therefore the script (which you might want to run as a scheduled task, even if only on demand) does not display the new recovery password to screen. You can easily modify it to show the password by removing “-WarningAction SilentlyContinue”.

$MountPoint = "C:"
# Register the event log source
$LogSource = "RCMTech"
New-EventLog -LogName Application -Source $LogSource -ErrorAction SilentlyContinue
# Get the key protectors
$KeyProtectors = (Get-BitLockerVolume -MountPoint $MountPoint).KeyProtector
foreach($KeyProtector in $KeyProtectors){
    if($KeyProtector.KeyProtectorType -eq "RecoveryPassword"){
        try{
            # Remove then re-add the RecoveryPassword protector
            Remove-BitLockerKeyProtector -MountPoint $MountPoint -KeyProtectorId $KeyProtector.KeyProtectorId | Out-Null
            # Assuming BitLocker is configured properly, the recovery password will be stored in Active Directory, don't display it on screen
            Add-BitLockerKeyProtector -MountPoint $MountPoint -RecoveryPasswordProtector -WarningAction SilentlyContinue | Out-Null
            # If we get this far, eveything has worked, write a success to the event log
            Write-EventLog -LogName Application -Source $LogSource -EntryType Information -EventId 1000 -Message "BitLocker Recovery Password for $MountPoint has been changed"
            Write-Host "Successfully changed BitLocker Recover Password" -ForegroundColor Green
        }
        catch{
            # Something went wrong, display the error details and write an error to the event log
            $Error[0]
            Write-EventLog -LogName Application -Source $LogSource -EntryType Warning -EventId 1001 -Message "Failed to change Bitlocker Recovery Password for $MountPoint"
        }
    }
}
Posted in PowerShell, Security, Windows | Tagged , , , , , , , , , | 1 Comment

Get or update SysInternals tools with PowerShell

This is version 2 – the previous version relied on mapping a new PSDrive directly to \\live.sysinternals.com\tools which made things easy, but that no longer seems to work (or at least not for me, might be my web filtering blocking it).

So as an alternative, here is one that pulls the files directly via HTTPS by parsing the listing from http://live.sysinternals.com/tools

$SysIntFolder = "C:\sysint"
$Page = (Invoke-WebRequest -Uri "https://live.sysinternals.com/tools").Content
$MatchedItems = ([regex]"<A HREF.*?<\/A>").Matches($Page)
$ItemCount = $MatchedItems.Count
$Copied = 0
foreach($Match in $MatchedItems.Value){
    
    if($Match -match ">(.*?\..*?)<"){
        $Matches[1]
        Write-Progress -Activity "Update SysInt" -Status $Matches[1] -PercentComplete ($Copied / $ItemCount * 100)
        Invoke-WebRequest -Uri ("https://live.sysinternals.com/tools/" + $Matches[1]) -OutFile (Join-Path -Path $SysIntFolder -ChildPath $Matches[1])
    }
    $Copied++

}
Write-Progress -Activity "Update SysInt" -Completed
Posted in PowerShell, Windows | Tagged , , , , , , | Leave a comment

Network List Manager in PowerShell

I’m writing a network diagnostic script, and wanted to use the network status and connectivity information that’s already been captured by the Network List Manager (NLM).

The code for pulling various bits of info is below. NLM_Connectivity was a good opportunity to learn how to work with enumeration of flags, so I’ve included several examples of working with these, to both list all the active connectivity types, and also ways to check for a specific connectivity type.

$NetworkListManager = [Activator]::CreateInstance([Type]::GetTypeFromCLSID(‘DCB00C01-570F-4A9B-8D69-199FDBA5723B’))

# Set enums for GetNetworks
$NLM_ENUM_NETWORK_CONNECTED=1
$NLM_ENUM_NETWORK_DISCONNECTED=2
$NLM_ENUM_NETWORK_ALL=3

$Networks = $NetworkListManager.GetNetworks($NLM_ENUM_NETWORK_CONNECTED)

foreach($Network in $Networks){
# Network name
$Network.GetName()

# Values from INetworkListManager interface https://msdn.microsoft.com/en-us/library/windows/desktop/aa370769(v=vs.85).aspx

# Network category
$NetCategories = New-Object -TypeName System.Collections.Hashtable
$NetCategories.Add(0x00,"NLM_NETWORK_CATEGORY_PUBLIC")
$NetCategories.Add(0x01,"NLM_NETWORK_CATEGORY_PRIVATE")
$NetCategories.Add(0x02,"NLM_NETWORK_CATEGORY_DOMAIN_AUTHENTICATED")
$NetCategories.Get_Item($Network.GetCategory())

# Domain type
$DomainTypes = New-Object -TypeName System.Collections.Hashtable
$DomainTypes.Add(0x00,"NLM_DOMAIN_TYPE_NON_DOMAIN_NETWORK")
$DomainTypes.Add(0x01,"NLM_DOMAIN_TYPE_DOMAIN_NETWORK")
$DomainTypes.Add(0x02,"NLM_DOMAIN_TYPE_DOMAIN_AUTHENTICATED")
$DomainTypes.Get_Item($Network.GetDomainType())

# NLM Connectivity
$NLMConnectivity = New-Object -TypeName System.Collections.Hashtable
$NLMConnectivity.Add(0x0000,"NLM_CONNECTIVITY_DISCONNECTED")
$NLMConnectivity.Add(0x0001,"NLM_CONNECTIVITY_IPV4_NOTRAFFIC")
$NLMConnectivity.Add(0x0002,"NLM_CONNECTIVITY_IPV6_NOTRAFFIC")
$NLMConnectivity.Add(0x0010,"NLM_CONNECTIVITY_IPV4_SUBNET")
$NLMConnectivity.Add(0x0020,"NLM_CONNECTIVITY_IPV4_LOCALNETWORK")
$NLMConnectivity.Add(0x0040,"NLM_CONNECTIVITY_IPV4_INTERNET")
$NLMConnectivity.Add(0x0100,"NLM_CONNECTIVITY_IPV6_SUBNET")
$NLMConnectivity.Add(0x0200,"NLM_CONNECTIVITY_IPV6_LOCALNETWORK")
$NLMConnectivity.Add(0x0400,"NLM_CONNECTIVITY_IPV6_INTERNET")

# Several methods for working with the connectivity flags

# Display all active connectivity types (method a)
foreach($Key in $NLMConnectivity.Keys){
$KeyBand = $Key -band $net.GetConnectivity()
if($KeyBand -gt 0){
$NLMConnectivity.Get_Item($KeyBand)
}
}

# Display all active connectivity types (method b)
$NLMConnectivity.Keys | Where-Object {$_ -band $Network.GetConnectivity()} | ForEach-Object {$NLMConnectivity.Get_Item($_)}

# Display all active connectivity types (method c)
switch ($Network.GetConnectivity()){
{$_ -band 0x0000}{"NLM_CONNECTIVITY_DISCONNECTED"}
{$_ -band 0x0001}{"NLM_CONNECTIVITY_IPV4_NOTRAFFIC"}
{$_ -band 0x0002}{"NLM_CONNECTIVITY_IPV6_NOTRAFFIC"}
{$_ -band 0x0010}{"NLM_CONNECTIVITY_IPV4_SUBNET"}
{$_ -band 0x0020}{"NLM_CONNECTIVITY_IPV4_LOCALNETWORK"}
{$_ -band 0x0040}{"NLM_CONNECTIVITY_IPV4_INTERNET"}
{$_ -band 0x0100}{"NLM_CONNECTIVITY_IPV6_SUBNET"}
{$_ -band 0x0200}{"NLM_CONNECTIVITY_IPV6_LOCALNETWORK"}
{$_ -band 0x0400}{"NLM_CONNECTIVITY_IPV6_INTERNET"}
}

# Display all active connectivity types (method d)
Enum NLM_CONNECTIVITY {
NLM_CONNECTIVITY_DISCONNECTED = 0x0000
NLM_CONNECTIVITY_IPV4_NOTRAFFIC = 0x0001
NLM_CONNECTIVITY_IPV6_NOTRAFFIC = 0x0002
NLM_CONNECTIVITY_IPV4_SUBNET = 0x0010
NLM_CONNECTIVITY_IPV4_LOCALNETWORK = 0x0020
NLM_CONNECTIVITY_IPV4_INTERNET = 0x0040
NLM_CONNECTIVITY_IPV6_SUBNET = 0x0100
NLM_CONNECTIVITY_IPV6_LOCALNETWORK = 0x0200
NLM_CONNECTIVITY_IPV6_INTERNET = 0x0400
}
[enum]::GetValues([NLM_CONNECTIVITY]) | Where-Object {$_.value__ -band $Network.GetConnectivity()}

# Check for a particular type of connectivity (method a)
if(0x0040 -band $Network.GetConnectivity()){Write-Host "NLM_CONNECTIVITY_IPV4_INTERNET is active"}

# Check for a particular type of connectivity (method b)
$NLMConnectivityActiveFlags = $NLMConnectivity.Keys | Where-Object {$_ -band $Network.GetConnectivity()} | ForEach-Object {$NLMConnectivity.Get_Item($_)}
if($NLMConnectivityActiveFlags.Contains("NLM_CONNECTIVITY_IPV4_SUBNET")){Write-Host "NLM_CONNECTIVITY_IPV4_SUBNET is active"}
}

Posted in PowerShell | Tagged , , , , , , , , , | 1 Comment

Group Policy Preference Drive Maps closing

I’ve been dealing with an issue where users that leave Windows File Explorer windows open for extended periods find that they close now and then.

This seems to be a common problem with later versions of Windows (8.1, 10) and is caused by the Replace setting being used on the Group Policy Preference Drive Map.

The File Explorer windows close because Replace causes the drive map to be removed and re-added. You want to use Replace because you also want to use the option to Remove this item when it is no longer applied. And also because Update will not change a drive mapping that has been done manually by a user.

Part of the fix is to set the following group policy:

Computer Configuration/Administrative Templates/System/Group Policy/Configure Drive Maps preference extension policy processing
Enabled:
Allow processing across a slow network connection: Enabled
Process even if the Group Policy objects have not changed: Disabled
Background priority: Idle

The key setting being Process even if the Group Policy objects have not changed, set to Disabled.

This will stop File Explorer closing on every group policy refresh.

This is only part of the solution though. You might think that this would cause the drive maps preference to only be processed if a drive map preference within the GPO has changed. That would seem sensible, right? Well, no. If you have other stuff in the GPO, and any of that changes, the drive maps will still be processed (i.e. removed and re-added) even though they’ve not changed.

You can see this by looking in Event Viewer, in the Microsoft-Windows-GroupPolicy/Operational log, and information event 4016. The text will say:

Starting Group Policy Drive Maps Extension Processing. 

List of applicable Group Policy objects: (Changes were detected.)

Your GPO Name

So what I’ve now done is to move my GP Pref drive mappings into a dedicated GPO, which I’ll hardly ever have to change. Thus the group policy engine will hardly ever detect any changes, and thus users will hardly ever see their File Explorer windows closing.

Posted in Windows | Tagged , , , , , , , , , , , , , , , , , , | Leave a comment

Don’t display last username if C drive encrypted with BitLocker

I’m moving from encrypting laptops with a third party disk encryption product to BitLocker. The third party product uses a pre-boot username & password prompt, and only boots into Windows if the credentials are correct. Users then have to log on to Windows using the Active Directory credentials. The last username is remembered so they just type in their AD password and the desktop loads.

With BitLocker I’m using the Trusted Platform Module (TPM) in the laptops to tie the disk to the laptop, rather than giving users and extra set of credentials to remember. When they turn on, BitLocker checks the TPM and the laptop hardware for changes, and if all is normal Windows will boot and present the user with the logon screen. However, currently this means that a lost or stolen laptop will boot and display the name of the last user, which I don’t want. If the username isn’t displayed, there are now two pieces of information required to get past the Windows Logon screen.

I’ve achieved this using a Group Policy Preference, targeted with a WMI query. The GPPref sets the dontdisplaylastusername policy registry value if the C: drive is encrypted, or removes the value if the drive is not encrypted – i.e. during the transition phase, users with the old disk encryption won’t be forced to type in their AD username at the Windows logon screen, they’ll still be remembered from their previous logon.

The configuration is as follows:
Computer Configuration – Preferences – Windows Settings – Registry
General tab:

Action: Replace
Hive: HKEY_LOCAL_MACHINE
Key Path: SOFTWARE\Microsoft\Windows\CurrentVersion\policies\system
Value: dontdisplaylastusername
Value type: REG_DWORD
Value Data: 00000001

Common tab:

Remove this item when it is no longer applied: ticked
Item-level targeting: ticked

Targeting…:
WMI query:

Query: select IsVolumeInitializedForProtection from Win32_EncryptableVolume where DriveLetter = 'C:' and IsVolumeInitializedForProtection = True
Namespace: root\CIMv2\Security\MicrosoftVolumeEncryption
Property: IsVolumeInitializedForProtection
Posted in Security, Storage, Windows | Tagged , , , , | Leave a comment

PowerShell: BitLocker Encryption Progress Bar

When enabling Bitlocker I want to know how far through the process of encrypting the drive it has got.

The script gets the encryption status from the manage-bde.exe command, parses it using a regular expression to get the percent complete, and goes into a loop updating a progress bar until the progress gets to 100%. Then the script quits.

Here’s the script:

function Get-BDEPercent{
    $BDEStatus = & manage-bde.exe -status c:
    $BDEStatus = $BDEStatus -join " "
    $Matches = $null
    $BDEStatus -match '\:\s([\d]{2,})\.\d\%' | Out-Null
    $Matches[1]
}

$Loop = $true

while($Loop){
    [int]$PercentComplete = Get-BDEPercent
    if($PercentComplete -ne 100){
        Write-Progress -Activity "Bitlocker Drive Encryption Status" -Status "Encrypting" -PercentComplete $PercentComplete
        Start-Sleep -Seconds 5
    }else{
        Write-Progress -Activity "Bitlocker Drive Encryption Status" -Completed
        $Loop = $false
    }
}
Posted in PowerShell, Storage, Windows | Tagged , , , , , , , , | Leave a comment

Storage Replica in Windows Server 2016

What is Storage Replica?

Block level synchronous or asynchronous, volume based data replication.

What does it do?

Replicates storage data from a disk attached to one instance of Windows Server to another disk attached to a different instance of Windows Server. Because it works at the block level, it doesn’t care about open files. It operates way down the driver stack:
storage-replica-driver-layering

It allows you to create stretch clusters (clusters that don’t have shared storage), or you can just use it to replicate data from one place to another.

How do you get it?

Storage Replica is new in Windows Server 2016. You need the Datacenter edition, but your Hyper-V/vSphere hosts are probably licensed for that anyway, right? It’a available in the “full” editions (i.e. with desktop or core) and also nano. It’s just a Windows Feature, so enable it with Server Manager or PowerShell:

Install-WindowsFeature -Name Storage-Replica

Synchronous vs Asynchronous

It’ll do both, you just need to pick the appropriate one. Firstly, is your network latency between the two servers greater than 5ms?  In which case you shouldn’t use Synchronous replication, the performance impact will be too great. If it’s 5ms or less, you can choose either, based on your needs.

Synchronous replicationstorage-replica-synchronous-replication

This works pretty much the same as oldskool SAN replication, kit like the EMC Clariion’s MirrorView feature does exactly the same thing (but for somewhat more than the cost of a Windows Server licence and some cheap server hardware – which is entertaining as certain models of Clariion actually ran Windows Server internally!). The key point with Synchronous replication is that the write is not confirmed on the source storage system until it has been written to the log disk on both the source and destination storage systems. This gives you peace of mind that your data is safe, at (usually) the expense of performance.

Asynchronous replication

See diagram for Synchronous replication, but confirm the write after step 2 – in other words, once the data has been written to the source log disk. The data is written to the destination log disk separately.

How Storage Replica changes your disk IO

On a volume that is being replicated via Storage Replica, all write activity happens to the Log disk. This data is then “destaged” to the Data disk, which thus only ever handles Because the Log disk is a log, the writes to this are sequential. Despite this, it’s recommended that you make the log disk an SSD – writes tend to slower than reads, and Windows (and applications, e.g. SQL Server, Exchange) can cache data for read operations in RAM. Write operations have to be securely written to disk, and so if the write speed of the log disk isn’t fast enough it’ll become the point of contention.

Is it safe to use now?

Probably – Microsoft has had customers running this technology on production systems since 2014. I plan on using it right now.

Want to know more?

Watch Ned Pyle’s excellent, informative, hilarious Ignite session video (where I stole the diagrams above from!). Hipsters beware…!

Posted in Storage, Uncategorized, Windows | Tagged , , , , , , , , | 1 Comment

Fix PowerShell WinRM remote connection errors

I’ve had two annoying PowerShell errors today, both for the same server:

Enter-PSSession : Connecting to remote server server-a.rcmtech.co.uk failed with the
following error message : The client cannot connect to the destination specified in
the request. Verify that the service on the destination is running and is accepting
requests. Consult the logs and documentation for the WS-Management service running
on the destination, most commonly IIS or WinRM. If the destination is the WinRM
service, run the following command on the destination to analyze and configure the
WinRM service: "winrm quickconfig". For more information, see the
about_Remote_Troubleshooting Help topic.

The standard “internet” response to this is to open an Administrator command prompt and run

winrm qc

(or winrm quickconfig). Or open an Administrator PowerShell prompt and run:

Enable-PSRemoting

But these are quite annoying solutions if you know that it should be working, and indeed is working on all your other servers, because you’ve configured it via Group Policy! There’s no harm in running the commands anyway, but most likely they’ll just come back and say “already configured” or words to that effect.

So the fix (for me) for the above error was to check what IP addresses the listener was listening on using:

netstat -aon | find "5985"

where 5985 is the default port used by WinRM. You should see the system process (ID 4) listening on 0.0.0.0 like this:

TCP 0.0.0.0:5985 0.0.0.0:0 LISTENING 4

However on my problem server it was listening on 127.0.0.1. This can be confirmed using:

netsh http show iplisten

and you’ll only see 127.0.0.1 in the list.
On the problem server itself you’ll find that using:

Enter-PSSession -Computername localhost

works, whereas:

Enter-PSSession -Computername server-a.rcmtech.co.uk

does not.
localhost is the name for the loopback address 127.0.0.1 whereas the fully qualified server name will give you the IP address of the server as seen from your network.
The fix for this is to delete the loopback address from the http listener, which then makes it listen on all valid addresses:

netsh http delete iplisten 127.0.0.1.

Check what addresses it is now listening on (plus the port) by using:

winrm e winrm/config/listener

Problem solved. Or not, I then got this error:

Enter-PSSession : Connecting to remote server server-a.rcmtech.co.uk failed with
the following error message : The WinRM client sent a request to an HTTP server and 
got a response saying the requested HTTP URL was not available. This is usually
returned by a HTTP server that does not support the WS-Management protocol. For
more information, see the about_Remote_Troubleshooting Help topic.

Which it turns out can be caused by having IPv6 enabled on the server.
In the netstat output (see above) I also had some IPv6 addresses showing as listening on port 5985.
I don’t use IPv6 but it’s on by default in Windows and will auto-assign itself a link local address (starts with fe80:). So I disabled IPv6 and the error went away. There’s also a workaround using the hosts file if you don’t want to disable IPv6.

Posted in PowerShell, Windows | Tagged , , , , , , , , , , , | Leave a comment