
90 results found with an empty search
- Windows Network Auditing: Finding the Process, Service, Port and IP Behind the Firewall Rule
Windows firewall troubleshooting is rarely as simple as allowing one executable and one port. An application may appear to use TCP 443, but the actual connection can be created by a Windows service, a child process, System, or one of the services running inside svchost.exe. The Network Process tools help expose that relationship by capturing Windows network endpoints and correlating them with processes, executable paths, command lines, services, ports and IP addresses. There are 2 versions - https://github.com/Tenaka/Windows-Network-Analysis-Tool/tree/main Network Process – Lite v2 Network Process – InDepth v2 Both produce the same core CSV reports, making it easy to start with Lite and move to InDepth when more evidence is required. What the tools capture The scripts repeatedly query the Windows TCP and UDP endpoint tables during a configurable capture period. They record: TCP connections and listeners UDP endpoints Local and remote IP addresses Local and remote ports TCP connection states Process IDs and process names Executable paths and command lines Parent processes Windows services hosted by each process First and last observation times The number of times each endpoint was seen They also inventory running processes, Windows services and configured network interface addresses. This provides a useful link between the network connection and the Windows component responsible for creating it. Lite or InDepth? The Lite version is designed for quick audits and routine fault finding. By default, it runs for 30 seconds and samples network endpoints every 500 milliseconds. It collects the core connection, process, service, IP address and port data without performing signature checks, DNS lookups or raw event logging. Lite is suitable when the problem is easy to reproduce and the objective is simply to identify the applications, services, ports and destinations involved. The InDepth version runs for 60 seconds by default and samples every 250 milliseconds. It also enables: Reverse DNS lookups Process owner collection Executable file metadata Authenticode signature checks Raw endpoint samples Connection event tracking The faster sampling gives InDepth a better chance of detecting short-lived connections during authentication, certificate validation, service discovery or application startup. SHA-256 file hashing is available but disabled by default because it increases disk access and processing overhead. Running the tools Run the scripts from an elevated Windows PowerShell session. & ".\network process - lite v2.ps1" For a longer capture: & ".\network process - lite v2.ps1" ` -DurationSeconds 120 ` -OutputFolder "C:\Logs\NetworkAudit" For a more detailed investigation: & ".\network process - indepth v2.ps1" ` -DurationSeconds 300 ` -OutputFolder "C:\Logs\NetworkAudit" Start the capture immediately before reproducing the fault. Launch the application, perform the login or connection attempt and continue until the failure occurs. Starting the script after the problem has happened may miss the connections that caused it. Understanding the reports The main report is Connections.csv. It shows the network endpoint together with the process, executable, service, local address, local port, remote address and remote port. Ports.csv presents the same evidence from a port-focused view, while IPAddresses.csv lists interface addresses and all IP addresses observed during the capture. Processes.csv shows the processes present during the audit and summarises their network activity. Services.csv links Windows services to the process IDs, ports and addresses they used. The InDepth version also produces Raw.csv and Events.csv. These show individual samples and connection changes such as when an endpoint first appeared, changed state or disappeared. Firewall troubleshooting The main purpose of the tools is to show what Windows is actually using rather than relying on the visible application name. For example, a VPN client may display its own executable, but parts of the authentication process may be performed by: svchost.exe System a background service a web authentication component a child process Allowing only the visible VPN executable through the firewall may therefore allow the connection to start but fail when the application reaches MFA, certificate validation or another Windows-hosted dependency. The reports can help answer: Which executable opened the connection? Which service was running inside svchost.exe? Which remote address and port were used? Did the connection appear only during login? Was the destination public, private, loopback or IPv6? Was the executable digitally signed? This evidence can be used to create a more targeted firewall rule instead of broadly allowing all traffic from svchost.exe. Interpreting ports correctly For a normal outbound HTTPS connection, the local port will usually be a temporary high-numbered port and the remote port will be TCP 443. The temporary local port should not normally be added to the firewall rule. It is automatically selected by Windows and changes between connections. A TCP listener is different. The local port is the port on which the computer is waiting for inbound connections. The reports label TCP entries as Connected or Listener, but they do not claim that every connected session is specifically inbound or outbound. Direction must be inferred from the local and remote ports, the application role and the firewall context. Working with svchost.exe A single svchost.exe process can host one Windows service or several services. The tools list all services associated with the process ID. When the svchost.exe command line identifies a specific service, the script can attribute the traffic to it. When several services share the same process and there is no clear service identifier, the report records the process as shared rather than guessing. This is intentional. A shared service process narrows the investigation, but it does not always prove which individual service created the connection. What the tools cannot prove These scripts are endpoint auditing tools. They are not packet analysers and they do not read firewall decisions. They cannot prove that: A firewall allowed or blocked a packet A remote server received the connection A TLS handshake completed Authentication succeeded A certificate was accepted Data was returned by the remote service Packet loss or routing failure occurred A connection stuck in SYN-SENT may indicate that Windows attempted to connect but received no reply. The cause could be a firewall, routing issue, unavailable server or incorrect destination. The results should therefore be compared with firewall logs, application logs, VPN gateway logs or packet capture when deeper evidence is required. Known limitations Both scripts use polling. A connection that opens and closes entirely between two samples may be missed. The InDepth version reduces this risk by sampling more frequently, but it cannot guarantee capture of every connection. UDP reporting is also limited. Windows exposes the local UDP endpoint, but the standard endpoint table does not provide the remote UDP peer. The scripts can show which process owns the local UDP port, but not where the UDP traffic was sent. Reverse DNS results should also be treated carefully. A returned name may belong to a CDN, proxy, cloud provider or load balancer and may not be the hostname originally requested by the application. Applications using proxies, secure web gateways or VPN tunnels may show only the gateway or proxy address rather than the final destination. Choosing the right tool Use Lite for quick checks, repeatable faults and initial firewall baselines. Use InDepth when: The fault is intermittent Authentication fails partway through svchost.exe is involved Connections are very short-lived Process ownership or signature information is required A detailed event timeline is needed These tools do not replace Wireshark or firewall logging. They provide the Windows process and service context that those tools often lack. That context is frequently the missing piece when an application has already been allowed through the firewall but still fails to work. Instead of asking only: Which port does this application need? the better questions become: Which process opened the connection? Was it the application, a child process, a service or svchost.exe? Which local and remote addresses were used? Was the port a listener, a destination port or an ephemeral source port? Did the dependency exist for the whole session or only during authentication? Answering those questions makes it possible to create firewall rules that are narrow enough to remain secure and complete enough to let the application work.
- Creating and Enforcing a Signed WDAC Policy with PowerShell
Windows Defender Application Control (WDAC) was previously known as DeviceGuard, now it's named App Control for Business. It is one of the strongest application-control technologies available in Windows. From this point forward, it will be referred to as WDAC. Creating a basic WDAC policy is relatively straightforward. Creating a signed WDAC policy that boots correctly, survives administrative tampering and can still be safely updated is considerably more challenging. I've lost count of the Windows systems that failed to survive signing and ended with a BSoD. The problem with devising a signed policy is that the information is spread across separate pages covering policy creation, rule options, certificate signing, deployment, Secure Boot and policy removal. Turning those individual components into a reliable end-to-end process required a fair amount of effort and trial and error, heavy on the error. This article describes the lab process used to: Scan a Windows 11 reference system. Create and test an audit policy. Convert it to unsigned enforcement. Add an authorised policy update signer. Sign the policy using SignTool. Deploy it to Windows and the EFI system partition. Confirm that removing the Windows policy file does not simply disable enforcement. Important: This is a lab proof of concept. The scripts and procedures described here are not currently suitable for an enterprise production deployment. Download and prepare the lab files Download the WDAC signing lab from: https://github.com/Tenaka/WDAC-Signing Extract the archive and copy the included subdirectories into: C:\WDAC The completed folder structure should contain the numbered scripts and the included Windows SDK signing files. Open Windows PowerShell or PowerShell ISE as an administrator, then run the scripts in numerical order, with reboots in between each script. When using the standard PowerShell, set the execution policy for the current lab session: Set-ExecutionPolicy -ExecutionPolicy Bypass -Force The repository includes the required SignTool.exe files. During the setup stage, these files are copied into the path expected by the signing scripts. Create the destination directory: New-Item ` -Path 'C:\Program Files (x86)\Windows Kits\10\bin\64' ` -ItemType Directory ` -Force Copy the included signing tools: Copy-Item ` -Path 'C:\WDAC\10.0.22621.0\x64\*' ` -Destination 'C:\Program Files (x86)\Windows Kits\10\bin\64' ` -Recurse ` -Force Once the files are in place, the lab scripts can locate and use SignTool.exe without requiring the full Windows SDK to be installed. What an unsigned WDAC policy does A WDAC policy enforces application-control and code-integrity rules that determine which kernel-mode drivers and user-mode code, including executables, DLLs, supported scripts and Windows Installer files, are permitted to run. These rules are deployed as compiled binary policy files. For this article, we will use the modern multiple-policy format, saved as {PolicyGUID}.cip. This format is used by current Windows 11 deployments and supports multiple base and supplemental policies, clearer policy identification and modern management through tools such as CiTool.exe. The older SiPolicy.p7b format is primarily associated with legacy single-policy deployments, older Windows versions and Group Policy-based management. The .cip format is not inherently more secure; it is used here because it provides the more flexible and current WDAC policy-management model. In the lab, the initial policy is generated by scanning the complete Windows system drive: New-CIPolicy ` -ScanPath 'C:\' ` -UserPEs ` -MultiplePolicyFormat ` -FilePath $AuditXml ` -Level FilePublisher ` -Fallback SignedVersion,Publisher,Hash The main rule level is FilePublisher, with fallbacks to: SignedVersion Publisher Hash This provides a useful balance for a lab system. Publisher-based rules are preferred where possible, while hashes provide a final fallback for unsigned or unusual files. The -UserPEs option includes user-mode executable files and enables user-mode code integrity, or UMCI. The XML policy is then converted into a deployable .cip file: ConvertFrom-CIPolicy ` -XmlFilePath $AuditXml ` -BinaryFilePath $PolicyCip The policy is deployed using CiTool.exe: CiTool.exe --update-policy $PolicyCip -json Microsoft documents CiTool.exe as the command-line utility for listing, updating and removing multiple-policy-format App Control policies. Audit mode comes first The first version of the lab policy is created in audit mode. Rule option 3 enables audit behaviour: Set-RuleOption -FilePath $AuditXml -Option 3 In audit mode, Windows records applications that would have been blocked, but it does not prevent them from running. This stage is critical. A filesystem scan only finds files that exist when the scan takes place. It does not automatically account for every file that might later be: Downloaded by an application. Extracted from an installer Created during an update. Loaded from a temporary directory. Unpacked from a driver package (HP Universal Drivers on first use). Before moving to enforcement, every required application, service, script and administrative tool should be launched, including HP printers with their first-use universal printer cab drivers. The lab review script collects relevant events from: Microsoft-Windows-CodeIntegrity/Operational The audit review includes event IDs: 3076 3089 3099 For example: Get-WinEvent -FilterHashtable @{ LogName = 'Microsoft-Windows-CodeIntegrity/Operational' Id = 3076,3089,3099 StartTime = (Get-Date).AddDays(-1) } Any required application appearing in the audit events must be resolved before enforcement is enabled. Moving to unsigned enforcement Once the audit events have been reviewed, the policy is copied and its version is increased: Copy-Item $AuditXml $EnforcedXml -Force Set-CIPolicyVersion ` -FilePath $EnforcedXml ` -Version '1.0.1.0' Audit mode is then removed: Set-RuleOption ` -FilePath $EnforcedXml ` -Option 3 ` -Delete The policy is converted again and redeployed with CiTool.exe. After restarting, applications that are not authorised by the policy are blocked. At this point, WDAC is enforcing application control, but the policy is still unsigned. Also at this point PowerShell will be in 'Constrained Language Mode' with unsigned scripts failing to load. The weakness of an unsigned WDAC policy An unsigned WDAC policy can provide effective application control, but it does not protect the policy itself. Rule option 6 is present by default: Enabled:Unsigned System Integrity Policy This option allows Windows to load the policy without a digital policy signature. Microsoft states that when option 6 is removed, the policy must be signed and its trusted update certificates must be defined in the UpdatePolicySigners section. The weakness is that a process running with sufficient administrative privileges can replace or remove any unsigned policy. The policy might successfully block users from running unauthorised software, but an administrator is able to remove the policy and eliminate its enforcement. Microsoft claims that unsigned policies allow malware to modify or remove WDAC, but that oversimplifies reality: WDAC is designed to stop untrusted code before it runs. In most cases, malware cannot simply launch a payload and delete the policy because the malware itself is blocked by the policy. The greater risk is administrative abuse. If an attacker gains local administrator or SYSTEM-level access, they may not need to run an obviously malicious executable. Instead, they can abuse trusted, Microsoft-signed tools and administrative interfaces already allowed by the system. This is commonly known as a Living off the Land technique. Tools such as CiTool.exe, PowerShell or other trusted management components may be used to modify or remove an unsigned WDAC policy through legitimate administrative mechanisms. An unsigned WDAC policy is therefore highly effective at stopping unauthorised applications and commodity malware. What it does not fully protect against is a trusted administrator—or an attacker operating with equivalent privileges—deliberately disabling the policy. In simple terms: An unsigned WDAC policy controls applications, but it does not securely control the administrator who owns the machine. For lightly managed systems this may be acceptable. For a locked-down system where the application-control policy must resist local administrative tampering, the policy needs to be signed. What changes when the WDAC policy is signed Signing the policy is not the same as signing an executable file: A signed WDAC policy introduces an authorised policy-update chain. The installed policy identifies one or more certificates that are allowed to sign future updates. Windows can then reject policy replacements that have not been signed by an authorised certificate. Microsoft describes signed App Control policies as providing its highest level of policy protection and helping prevent policy tampering or removal, including by an administrator. This changes the security model: Unsigned policy Windows loads the policy without a policy signature. The policy can enforce executable, script and driver rules. An administrator can remove or replace the policy. Removing the active policy removes its enforcement. Signed policy Windows validates the policy’s PKCS#7 signature. Future policy updates must be signed by an authorised update signer. An arbitrary unsigned replacement policy is rejected. The policy can be protected through the Secure Boot and EFI boot process. Improper removal can cause a boot failure rather than cleanly disabling enforcement. Lab prerequisites The working lab uses: Windows 11. UEFI firmware. Secure Boot enabled. Windows PowerShell 5.1. The built-in ConfigCI PowerShell module. CiTool.exe. A code-signing certificate. Microsoft SignTool, seperate download. A disposable virtual machine or test system. At least one checkpoint taken before signed enforcement. The script stops immediately if Secure Boot is not enabled: if (-not (Confirm-SecureBootUEFI)) { throw 'Secure Boot is not enabled.' } It also verifies that it is running in Windows PowerShell rather than PowerShell 7: if ($PSVersionTable.PSEdition -ne 'Desktop') { throw 'Run this procedure from Windows PowerShell 5.1.' } SignTool is required Microsoft SignTool is required to produce the signed WDAC policy. SignTool is included with the Windows SDK and is installed under the SDK Bin directory. The exact path varies depending on the SDK version and installed components. The lab uses: C:\Program Files (x86)\Windows Kits\10\bin\64\signtool.exe The initial scan script checks for SignTool before beginning the full C:\ scan: if (-not $SignTool -or -not (Test-Path -LiteralPath $SignTool -PathType Leaf)) { throw 'SignTool.exe was not found. Install the Windows SDK Signing Tools before scanning C:\.' } This prevents the system from completing the long scan and audit process only to discover later that the required signing tool is unavailable. Creating the lab signing certificate The lab creates a self-signed code-signing certificate in the Local Machine certificate store: $Certificate = New-SelfSignedCertificate ` -Type CodeSigningCert ` -Subject 'CN=Tenaka WDAC Lab Policy Signer' ` -FriendlyName 'Tenaka WDAC Lab Policy Signer' ` -CertStoreLocation 'Cert:\LocalMachine\My' ` -KeyAlgorithm RSA ` -KeyLength 3072 ` -HashAlgorithm SHA256 ` -KeyExportPolicy Exportable ` -Provider 'Microsoft Software Key Storage Provider' ` -NotAfter (Get-Date).AddYears(5) The public certificate is exported as a .cer file: Export-Certificate ` -Cert $Certificate ` -FilePath $CerPath ` -Force The certificate and private key are also backed up as a password-protected .pfx file: Export-PfxCertificate ` -Cert $Certificate ` -FilePath $PfxPath ` -Password $PfxPassword ` -ChainOption EndEntityCertOnly ` -CryptoAlgorithmOption AES256_SHA256 ` -Force The script validates: The private key exists. The key uses RSA. The RSA key size is supported. The certificate contains the Code Signing EKU. The exported public certificate matches the certificate containing the private key. The Code Signing EKU is: 1.3.6.1.5.5.7.3.3 This is acceptable for a controlled lab. It is not how an enterprise signing key should normally be managed. Adding the authorised update signer The unsigned enforced policy is copied to create the signed-policy XML: Copy-Item $EnforcedXml $SignedXml -Force The certificate is then added as an update signer: Add-SignerRule ` -FilePath $SignedXml ` -CertificatePath $CerPath ` -Update The -Update switch is important. It creates an entry in the policy’s UpdatePolicySigners section. This tells Windows which certificate is authorised to sign future policy versions. The script validates that at least one update signer exists: $UpdateSignerCount = @( $SignedPolicyDocument.SiPolicy.UpdatePolicySigners.UpdatePolicySigner ).Count if ($UpdateSignerCount -lt 1) { throw 'No UpdatePolicySigner was found.' } Signing a policy without correctly configuring its update signer can make future policy updates or removal extremely difficult. Removing unsigned-policy support The policy version is increased again: Set-CIPolicyVersion ` -FilePath $SignedXml ` -Version '1.0.2.0' Rule option 6 is then removed: Set-RuleOption ` -FilePath $SignedXml ` -Option 6 ` -Delete Removing option 6 changes the policy from a policy that may be unsigned into one that must be signed. Microsoft documents that removing option 6 requires the policy and any applicable supplemental policies to be signed. It also requires trusted future update certificates to be defined in the policy. The lab also confirms that supplemental policies are not enabled: Set-RuleOption ` -FilePath $SignedXml ` -Option 17 ` -Delete ` -ErrorAction SilentlyContinue This proof of concept uses a single base policy rather than a base-and-supplemental-policy design. Lab recovery options Two recovery-related policy options are retained: Set-RuleOption -FilePath $SignedXml -Option 9 Set-RuleOption -FilePath $SignedXml -Option 10 These represent: Enabled:Advanced Boot Options Menu Enabled:Boot Audit on Failure Option 9 allows the advanced boot menu to be made available to a physically present user. Option 10 allows Windows to switch the policy into audit mode when a boot-critical driver is blocked, helping the operating system start instead of immediately failing. Microsoft documents both behaviours in its policy rule-option reference. These options are useful during development and testing. Their use in a production security baseline should be deliberately reviewed. Converting and signing the policy The signed XML is first converted into a binary .cip file: ConvertFrom-CIPolicy ` -XmlFilePath $SignedXml ` -BinaryFilePath $PolicyCip SignTool then generates a detached PKCS#7 signature. The WDAC policy content OID is: 1.3.6.1.4.1.311.79.1 The working signing command is: & $SignTool sign ` /v ` /sm ` /s My ` /sha1 $CertificateThumbprint ` /fd SHA256 ` /p7 $SignedOutput ` /p7co 1.3.6.1.4.1.311.79.1 ` $PolicyCip The relevant switches are: /v enables verbose output. /sm selects the Local Machine certificate store. /s My selects the Personal certificate store. /sha1 selects the exact certificate by thumbprint. /fd SHA256 uses SHA-256 as the file digest algorithm. /p7 creates detached PKCS#7 output. /p7co specifies the WDAC policy content OID. Microsoft’s signed-policy procedure uses SignTool with /p7, the WDAC content OID and a supported digest algorithm. SignTool creates a file named using the original filename with .p7 appended: {PolicyID}.cip.p7 The script copies that signed output into the final policy directory and renames it to: {PolicyID}.cip The .cip extension does not mean the file is still the original unsigned binary. The final file contains the PKCS#7-signed policy content. Verifying the signed policy The lab doesn't assume that a successful SignTool exit code is sufficient. The public certificate is temporarily trusted on the lab system, and SignTool is used to verify the PKCS#7 signature: & $SignTool verify ` /p7 ` /v ` /debug ` $FinalSignedCip The policy is also parsed using: certutil.exe -asn $FinalSignedCip Finally, its SHA-256 hash is recorded: Get-FileHash ` $FinalSignedCip ` -Algorithm SHA256 This provides a known hash for the final signed policy before deployment. Deploying the signed policy The first signed policy is copied to the Windows Code Integrity policy directory: C:\Windows\System32\CodeIntegrity\CiPolicies\Active The script then mounts the EFI system partition and copies the same policy to: EFI\Microsoft\Boot\CiPolicies\Active The EFI partition is located using: $EfiPartition = ( Get-Partition | Where-Object IsSystem | Select-Object -First 1 ).AccessPaths[0] It is mounted with: mountvol.exe C:\EFIMount $EfiPartition The signed policy is then copied into the EFI policy directory. Microsoft identifies both the Windows Code Integrity directory and the EFI system partition as possible policy locations for multiple-policy-format App Control policies. The script calculates the hash of: The original signed policy. The Windows copy. The EFI copy. Deployment is stopped if the three hashes do not match. if (($SourceHash -ne $OsHash) -or ($SourceHash -ne $EfiHash)) { throw 'Source, OS and EFI hashes do not match.' } The EFI partition is unmounted and the computer is restarted. What happened when the Windows policy was deleted The most important lab result came after signed enforcement was confirmed. The signed policy file was deleted from: C:\Windows\System32\CodeIntegrity\CiPolicies\Active Applications that were not allowed by the policy continued to be blocked. Deleting the Windows copy did not disable enforcement because the signed policy had also been deployed into the EFI system partition. This demonstrates an important difference between removing a file and performing an authorised policy removal: Deleting the operating-system copy of a signed WDAC policy is not the same as removing the active policy. The policy remained available through: EFI\Microsoft\Boot\CiPolicies\Active and continued to participate in the Secure Boot-backed startup process. This should not be interpreted as meaning that every possible deletion of every signed-policy copy will always leave Windows running normally. The accurate conclusion is: Removing only the Windows copy may not disable the policy when an EFI copy remains. An unsigned replacement cannot simply overwrite the signed policy. Future updates must be signed by an authorised update signer. Improperly removing all copies of an active signed policy may result in a boot failure rather than a clean removal. Microsoft specifically warns that signed policies must be removed through the supported replacement process. How a signed policy is removed correctly A signed base policy should not simply be deleted. Microsoft’s documented removal process requires a signed replacement policy that uses the same policy identity and re-enables rule option 6. At a high level: Start with the current signed-policy XML. Retain the same PolicyID. Increase, or at least retain, the existing policy version. Add option 6 back to the policy. Retain the authorised update signer. Convert the replacement policy to .cip. Sign it using a certificate authorised by the existing policy. Deploy the signed replacement. Restart the computer. Remove the policy using CiTool.exe or the appropriate policy-removal process. The replacement is still signed, allowing the currently installed policy to authorise the update. However, the replacement re-enables unsigned-policy support. After the required restart, the Secure Boot protection is deactivated and the policy can be removed normally. Losing the signing private key could therefore create a serious recovery problem. The certificate, private key and recovery procedure must be protected before signed enforcement is deployed. Post-signing validation After restarting, the lab checks Secure Boot: Confirm-SecureBootUEFI It then records the active policies: CiTool.exe --list-policies -json The target policy should report values equivalent to: IsSignedPolicy : True IsEnforced : True IsAuthorized : True IsOnDisk : True The post-signing review also collects events: 3077 3089 3099 Event 3077 is particularly important because it records code that was blocked while the policy was in enforcement mode. The policy should then be tested across two complete shutdown and startup cycles. Required applications should continue to work, while deliberately unauthorised applications should remain blocked. Final thoughts An unsigned WDAC policy does provide effective application control. It can block unauthorised executables, scripts, DLLs and drivers, but it does not fully protect the policy itself. A local administrator, is able to remove the policy and eliminate its enforcement. Signing the WDAC policy changes that security model. The policy defines which certificate is authorised to approve future versions, and Windows validates the signed policy during startup. When the policy is also deployed to the EFI system partition, its enforcement becomes tied to the Secure Boot-backed startup process. This additional protection does, however, introduce risk and administrative overhead. The signing certificate and private key become critical security assets. They must be securely stored, backed up and available whenever the policy needs to be updated or removed. If the authorised signing key is lost, expired or unavailable, maintaining the policy can become extremely difficult. Every policy change must also follow a controlled lifecycle. The policy version must be managed correctly, the update signer must remain valid, the replacement policy must be signed, and the deployment and recovery process must be tested before the change reaches production systems. A mistake in an unsigned policy may block an application. A mistake in a signed policy can prevent the operating system from starting. Signed WDAC therefore provides a much stronger application-control boundary, particularly on systems where local administrators should not be able to bypass enforcement. That protection comes at the cost of additional certificate management, change control, testing, recovery planning and operational responsibility. For tightly controlled or high-security devices, that overhead may be justified. For general-purpose systems, an unsigned policy may provide a more practical balance between application control, supportability and recovery.
- Understanding Windows File Altitude: A Deep Dive into File System Filter Drivers
When delving into the intricate workings of Windows file system architecture, one of the more technical concepts that often emerges is file altitude. If you’ve ever explored file system filter drivers or engaged in low-level system development, understanding this concept is crucial. This blog aims to break down the complexities of Windows file altitude, the role it plays in the kernel, and how it affects file system operations. What is a Windows File System Filter Driver? Before diving into file altitude, it’s essential to understand the role of file system filter drivers. In Windows, a filter driver operates within the kernel mode and can monitor, modify, or extend the functionality of file system operations. These drivers can be inserted into the I/O request path, between the application and the underlying file system, to intercept and possibly modify file operations such as read, write, and delete requests. File system filter drivers are typically used for: Antivirus solutions: to monitor and block malicious activities. File encryption or compression: to apply encryption or compression on the fly. Backup solutions: to intercept and manage file access for consistent backups. File system auditing or monitoring: for logging file system activities or imposing policies. Introducing the Concept of File Altitude In a system where multiple filter drivers are installed, there needs to be a way to define their order of operation. This is where altitude comes into play. Simply put, file altitude is a numerical value that dictates the position of a filter driver within the file system stack. The higher the altitude, the closer a driver is to the application layer (and further from the actual file system). Windows ensures that these altitudes are registered and properly sequenced to avoid conflicts between drivers that might need to operate in a specific order. How Altitude Works Imagine a scenario where multiple drivers are installed for various purposes (e.g., an antivirus, a backup tool, and a logging tool). These drivers all want to interact with I/O requests. Without an ordering mechanism, there could be conflicts: An antivirus might want to inspect a file before any backup software reads it. The backup software might need to know the original state of a file before encryption is applied. Altitude values help resolve this by assigning each filter driver a priority based on its altitude. Windows ensures that the drivers with the highest altitudes receive I/O requests first, while those with lower altitudes are closer to the file system (and see the request last). Altitude Numbering System The altitude value is a floating-point number ranging from 0.000000 to 999999.999999. By convention, the lower the altitude number, the closer the driver is to the file system itself, and the higher the number, the closer it is to user mode operations. Upper-range altitudes (e.g., 380000-499999) are typically reserved for drivers like encryption and compression tools that need to operate closer to user-mode applications. Middle-range altitudes (e.g., 200000-379999) are often used by antivirus software, which needs to filter I/O requests before they reach the disk. Lower-range altitudes (e.g., 0-199999) are usually occupied by drivers that need to interact closely with the file system itself, such as volume managers and file system encryption. Each filter driver registered with the system must provide a unique altitude to prevent collisions or ordering issues. Managing Altitude in Windows The Windows OS provides a centralized mechanism for managing filter driver altitudes. Filter Manager, a built-in component of Windows starting from Windows Server 2003, facilitates the registration and sequencing of these filter drivers. It ensures that drivers operate in the correct order based on their altitude, preventing lower-altitude drivers from inadvertently disrupting higher-altitude ones. Querying Altitude You can query a system's file system filter driver altitudes using the `fltmc` utility in the command prompt. This utility displays loaded filter drivers, their altitudes, and their current operational state. fltmc filters fltmc instances The output of this command might look like: Registering a Driver with an Altitude When developing or installing a new file system filter driver, you need to register the driver with an appropriate altitude to ensure that it functions correctly within the filter stack. The driver installation process typically handles this via INF files or registry entries. Altitudes are not chosen arbitrarily; they are managed and assigned by Microsoft. Developers must register for an altitude by contacting Microsoft’s filter manager team to ensure that no two drivers conflict by using the same altitude. Handling Altitude Conflicts Altitude conflicts can arise when two or more drivers attempt to register for the same or similar altitudes, especially if one driver isn’t aware of the other. If a conflict occurs: It can lead to unpredictable system behavior, including I/O request handling errors. In worst-case scenarios, it could result in BSODs (Blue Screens of Death) due to improper sequencing of I/O operations. By adhering to the altitude registration process, conflicts are minimized. The filter manager enforces altitude uniqueness to prevent these kinds of operational failures. Practical Example: Antivirus and Backup Solutions Consider a scenario where an antivirus solution and a backup tool are installed on the same machine: Antivirus Filter: This filter driver operates at an altitude of 350000. When an application requests to read or write a file, the antivirus filter intercepts the request first. It scans the file for malicious content before passing it down the stack. Backup Filter: This filter driver is at altitude 250000. After the antivirus completes its scanning, the request moves to the backup filter, which monitors the file for any changes, making a backup copy if necessary. File System Operations: Finally, the request is passed down to the actual file system, which handles the physical read or write operations. Without the correct altitude order, the backup software might try to back up a file before it has been scanned by the antivirus software, potentially saving a corrupted or infected file. Conclusion In summary, file altitude is a critical mechanism in the Windows file system architecture that governs the order in which filter drivers process I/O requests. By assigning a specific altitude to each filter driver, Windows ensures that drivers operate in the correct sequence, minimizing conflicts and ensuring the integrity of file system operations. Whether you're developing file system tools or managing enterprise-level systems, understanding and properly handling file altitude is crucial for maintaining system stability and security.
- Tested to the Limit: Finding the Fastest USB Flash Drive
In this article, I’ll compare 11 USB pens to find out which one deserves to be crowned the fastest. Buying a USB flash drive is often treated as a simple choice based on capacity and price, but performance can vary dramatically between products that appear similar on the packaging. Two 64 GB USB 3.x drives may cost almost the same, yet one can complete a large transfer several times faster than the other. Advertised figures usually emphasise maximum read speed, while sustained write speed, cache behaviour, and mixed-file performance are often less obvious. A cheaper drive may save a few pounds at purchase but cost far more in waiting time when copying installation media, deployment files, backups, or large datasets. Choosing the right USB drive therefore means looking beyond capacity and connector type and considering how it performs with the workload it will actually be used for. This test was designed to compare a selection of USB flash drives using the same computer, source files, destination paths, filesystem, and PowerShell-based test process. The aim was not to reproduce a laboratory storage benchmark, but to measure the time taken to complete common file-copy operations under controlled and repeatable conditions. USB Devices Tested Device all purchased before 20 June 26 from Amazon Cost Advertised Read (MBs) Advertised Write (MBs) Type 1 Kingston DataTraveler Kyson USB 3.2 Gen 1 64GB £16-00 60 200 USB-A 2 Kingston DataTraveler Exodia DTX/64GB-2P Flash Drive USB 3.2 Gen 1 £8-48 60 100 USB-A 3 Generic USB Stick 64GB USB 3.0 Flash Drive £9-99 70 20 USB-A 4 Kingston DataTraveler G4 - DTIG4/64GB USB 3.1 £13-58 60 10 USB-A 5 Integral USSD 256GB Turbo-C USB 3.2 Gen 2x2 £49-95 2000 1300 USB-C 6 OSCOO SU001 128GB - USB-A & USB-C 3.2 (Dual Connector) £39-99 500 550 USB-A and USB-C 7 SanDisk Ultra Dual Drive Go, USB Type-C & Type-A Flash Drive 128GB £21-85 400 150 USB-A and USB-C 8 SanDisk Ultra Flair 64GB USB 3.0 £15-08 150 150 USB-A 9 Amazon Basics 128 GB, USB 3.1 £21-49 130 30 USB-A 10 Kingston DataTraveler 70 - DT70/128GB £12-65 400 625 USB-C 11 Amazon Basics USB Flash Drive USB C and USB A Dual Ports £25-70 400 not known USB-A and USB-C Test computer All tests were carried out on a 2024 ASUS ROG Zephyrus G16. The laptop provides USB-A, USB-C, and Thunderbolt 4 connections, allowing compatible drives and dual-connector devices to be tested through the different connection types available on the same computer. A second Samsung 990 Pro NVMe SSD was installed and dedicated to the USB performance tests. The Windows operating system, applications, page file, and normal background activity remained on the primary system drive. The benchmark source files and local read-back destination were stored on the dedicated Samsung 990 Pro. This reduced the likelihood of operating-system activity competing with the benchmark for storage access and ensured that the local source and destination were considerably faster than the USB devices being tested. The same source files and folder locations were used for every device. Test process Each USB device was formatted as NTFS using the default allocation unit size before testing. NTFS was required because the test included individual files larger than the 4 GB maximum file size supported by FAT32. The benchmark was run using a PowerShell script written specifically for the test. At the start of each session, the script prompts for: The name and model of the USB device. The connection or port type being used. The target drive, which was normally assigned as E:\. The script then performs a write test by copying each test workload from the dedicated Samsung 990 Pro to the USB device. Once the write has completed, it performs a read test by copying the same data from the USB device back to a separate folder on the dedicated local SSD. Only the actual copy operation is timed. File discovery, destination preparation, result validation, and cleanup are performed outside the timed section. Robocopy is used to perform the transfers. Large-file tests use unbuffered I/O to reduce the effect of the Windows file cache. The mixed-file test uses a single copy thread so that every device is tested with the same level of concurrency. After each operation, the script verifies that the destination contains the expected number of files and total number of bytes. The elapsed time, calculated transfer rate, test type, direction, device name, connection type, filesystem, disk information, and host details are written to CSV files. Each device and connector combination is recorded as a separate test session. This is particularly important for drives with both USB-A and USB-C connectors, as the two interfaces can be compared independently. Test workloads Three different workloads were selected. 5 GB ISO file The ISO test represents a typical large, continuous file such as Windows installation media, a recovery image, or a software distribution image. A single large file gives a useful indication of sequential transfer performance. Its size is also large enough to expose some short-duration cache behaviour while remaining practical to repeat across several devices. 30 GB single file The 30 GB file is used to measure sustained sequential performance. Many flash drives can write quickly for the first few gigabytes by using a faster cache area. Once that cache is full, the transfer rate may fall significantly. A 30 GB file is large enough to show whether the advertised or initial write speed can be maintained throughout a longer transfer. This test is therefore particularly useful when comparing short-burst write performance with sustained write performance. Mixed-file collection The mixed-file test contains 8 GB of data spread across 2,166 files. This workload represents a more typical collection of deployment scripts, drivers, applications, documents, configuration files, and supporting content. Copying many files introduces additional filesystem and metadata operations, so performance can be substantially lower than when copying one large file. This test is relevant to USB drives used for Windows deployment, technical support, software installation, and general file storage, where the workload is rarely limited to a single continuous file. Recorded results For each workload, the test records: Write speed from the dedicated local SSD to the USB device. Read speed from the USB device back to the dedicated local SSD. Total elapsed time. Decimal megabytes per second. Binary mebibytes per second. File count and total bytes. Device and connection details. Pass or fail validation status. The resulting figures are then compared with the manufacturers’ advertised transfer speeds. Where a manufacturer only publishes an advertised read speed, the measured write result is reported separately rather than being compared against an unsupported write claim. They are intended to show how the drives compare when completing the same practical workloads, rather than to represent the maximum theoretical bandwidth of the USB or Thunderbolt connection. Advertised speed comparison The advertised figures supplied with the test list were used as the comparison baseline, with one important adjustment. Some entries appear to have the advertised read and write columns reversed when compared with typical manufacturer wording. For consistency, the comparison below uses normalised read/write values where required. The most important figure in this report is the 30 GB sustained write speed, because this shows how the device behaves once any short-term cache benefit has been exhausted. Rank Device and the best tested connection Advertised read/write 30 GB read Read achieved 30 GB write Write achieved 30 GB write time 1 Integral USSD 256GB Turbo Thunderbolt 4 2000 / 1300 2043 MB/s 102% 509 MB/s 39% 1m 03s 2 OSCOO SU001 128GB USB-C 550 / 500 528 MB/s 96% 442 MB/s 88% 1m 13s 3 SanDisk Ultra Dual Drive Go Thunderbolt 4 400 / 150 412 MB/s 103% 71 MB/s 47% 7m 33s 4 Amazon Basics USB-C and USB-A Dual Port Thunderbolt 4 400 / unknown 313 MB/s 78% 38 MB/s — 13m 57s 5 SanDisk Ultra Flair 64GB USB 3.0 USB-A 150 / 150 153 MB/s 102% 33 MB/s 22% 16m 02s 6 Amazon Basics 128GB USB 3.1 USB-A 130 / 30 141 MB/s 108% 30 MB/s 100% 17m 57s 7 Generic USB Stick 64GB USB 3.0 USB-A 70 / 20 111 MB/s 158% 27 MB/s 137% 19m 37s 8 Kingston DataTraveler Kyson USB-A 200 / 60 226 MB/s 113% 21 MB/s 34% 26m 04s 9 Kingston DataTraveler 70 DT70 Thunderbolt 4 400 / 625* 87 MB/s 22% 15 MB/s 2% 35m 33s 10 Kingston DataTraveler Exodia USB-A 100 / 60 94 MB/s 94% 14 MB/s 23% 38m 39s 11 Kingston DataTraveler G4 USB-A 60 / 10 105 MB/s 175% 12 MB/s 118% 45m 24s *The Kingston DataTraveler 70 DT70 advertised figures require verification. The supplied figures do not align with the measured behaviour of the tested device, so the percentage comparison should be treated only as a comparison against the supplied listing values. Main finding The advertised read speeds were generally much closer to the measured results than the advertised write speeds. Several devices met or exceeded their advertised read speed during the sustained 30 GB read test. This included the Integral Turbo-C, SanDisk Ultra Dual Drive Go, SanDisk Ultra Flair, Amazon Basics 128GB, Generic USB stick, Kingston Kyson, and Kingston G4. Write performance was much less consistent. The biggest difference between advertised and measured performance appeared during the 30 GB write test. The Integral Turbo-C reached 2043 MB/s read, slightly exceeding its advertised 2000 MB/s read figure. However, its sustained 30 GB write speed was 509 MB/s, which is only 39% of the supplied 1300 MB/s write figure. The OSCOO SU001 was the strongest sustained performer relative to its advertised figures. It reached 528 MB/s read and 442 MB/s write, achieving approximately 96% of its advertised read speed and 88% of its advertised write speed. Sustained write performance The 30 GB write test produced the clearest separation between devices. The fastest device, the Integral Turbo-C, completed the 30 GB write in 1 minute and 3 seconds. The slowest device, the Kingston DataTraveler G4, required 45 minutes and 24 seconds. That makes the fastest result approximately 43 times faster than the slowest result for the same file, on the same laptop, using the same source SSD and the same test script. For Windows deployment work, this difference is significant. A slow USB device can add a large amount of waiting time when copying operating system images, application installers, driver packs, scripts, and deployment content. Cache behaviour The 5 GB ISO test and 30 GB file test show whether a device can maintain its initial write speed over a longer transfer. Device 5 GB write 30 GB write Reduction SanDisk Ultra Flair 81 MB/s 33 MB/s 58% lower Integral Turbo-C 1219 MB/s 509 MB/s 58% lower SanDisk Ultra Dual Drive Go 161 MB/s 71 MB/s 56% lower Amazon Basics USB-C and USB-A Dual Port 51 MB/s 38 MB/s 24% lower The Integral Turbo-C almost reached its advertised write speed during the 5 GB ISO test, writing at 1219 MB/s. Over the larger 30 GB file, the average dropped to 509 MB/s. This suggests the device is capable of very high short-burst writes, but that speed is not maintained across a larger sustained transfer. The OSCOO SU001 behaved differently. It wrote the 5 GB ISO at 444 MB/s and the 30 GB file at 442 MB/s. That makes it one of the most consistent devices tested. Mixed-file performance The mixed-file test used 8 GB of data across 2,166 files. This is closer to a real deployment USB containing applications, scripts, drivers, configuration files, and supporting folders. Rank Device and the best tested connection Mixed write Mixed read 1 Integral Turbo-C — Thunderbolt 4 552 MB/s 1312 MB/s 2 OSCOO SU001 — USB-C 323 MB/s 444 MB/s 3 SanDisk Ultra Dual Drive Go — Thunderbolt 4 72 MB/s 335 MB/s 4 Amazon Basics USB-C and USB-A Dual Port — USB-C 41 MB/s 286 MB/s 5 SanDisk Ultra Flair — USB-A 32 MB/s 139 MB/s 6 Amazon Basics 128GB USB 3.1 — USB-A 24 MB/s 130 MB/s 7 Generic USB Stick 64GB USB 3.0 — USB-A 22 MB/s 104 MB/s 8 Kingston DT70 — Thunderbolt 4 13 MB/s 82 MB/s 9 Kingston Exodia — USB-A 13 MB/s 89 MB/s 10 Kingston Kyson — USB-A 6 MB/s 177 MB/s 11 Kingston G4 — USB-A 3 MB/s 43 MB/s The mixed-file test was particularly damaging for the Kingston G4 and Kingston Kyson. The Kyson read performance was strong at 177 MB/s for mixed files, but its mixed-file write speed was only 6 MB/s. This makes it unsuitable for workloads that involve repeatedly writing large collections of smaller files. The Kingston G4 performed worst overall in the mixed-file write test at 2.75 MB/s. Connector comparison Several dual-connector or USB-C devices were tested through more than one connection type. OSCOO SU001 Connection 30 GB read 30 GB write Mixed write USB-C 528 MB/s 442 MB/s 323 MB/s USB-A 524 MB/s 432 MB/s 314 MB/s Thunderbolt 4 port 524 MB/s 411 MB/s 238 MB/s The OSCOO produced very consistent large-file results across USB-A and USB-C. It did not benefit from the Thunderbolt 4 port because the device itself is the limiting factor rather than the laptop port. SanDisk Ultra Dual Drive Go Connection 30 GB read 30 GB write Mixed write Thunderbolt 4 port 412 MB/s 71 MB/s 72 MB/s USB-A 400 MB/s 70 MB/s 52 MB/s USB-C 412 MB/s 69 MB/s 53 MB/s The SanDisk Ultra Dual Drive Go was consistent across all connector types for large-file read and write performance. The Thunderbolt 4 connection did not materially improve large-file performance, indicating that the USB drive is the limiting factor. Amazon Basics USB-C and USB-A Dual Port Connection 30 GB read 30 GB write Mixed write USB-C 319 MB/s 38 MB/s 41 MB/s Thunderbolt 4 port 313 MB/s 38 MB/s 41 MB/s USB-A 286 MB/s 38 MB/s 40 MB/s The Amazon Basics dual-port drive delivered consistent write performance across all three tested connections. Read performance was better through USB-C and Thunderbolt 4 than USB-A, but write speed remained around 38 MB/s regardless of connector. This makes it a reasonable read-focused device, but not a strong choice for repeated large writes. Integral Turbo-C Connection 30 GB read 30 GB write Mixed write Thunderbolt 4 port 2043 MB/s 509 MB/s 552 MB/s USB-A via adapter 1065 MB/s 467 MB/s 368 MB/s USB-C 1068 MB/s 464 MB/s 410 MB/s The Integral Turbo-C was the fastest device tested. It delivered the highest overall read performance through the Thunderbolt 4 port, reaching 2043 MB/s on the 30 GB read test. It also produced the fastest sustained write result, completing the 30 GB write at 509 MB/s. Performance remained strong through both USB-C and the USB-A adapter. Over USB-C, the drive reached 1068 MB/s read and 464 MB/s sustained write. Through the USB-A adapter, it produced a very similar large-file result, reaching 1065 MB/s read and 467 MB/s sustained write. Thunderbolt 4 provided the strongest overall performance, especially for read speed and mixed-file transfers. However, the USB-C and USB-A adapter results show that the Integral Turbo-C remains a very fast option even when Thunderbolt is not available Kingston DataTraveler 70 Connection 30 GB read 30 GB write Mixed write Thunderbolt 4 port 87 MB/s 15 MB/s 13 MB/s USB-C 29 MB/s 10 MB/s 9 MB/s The Kingston DataTraveler 70 DT70 was one of the weakest performers in the test. Its read speed was consistent at around 84–87 MB/s, but its write speed remained between 13–15 MB/s across all workloads. The 30 GB sustained write completed at 14.64 MB/s and took 36 minutes and 41 seconds. Price-to-performance Using sustained 30 GB write speed divided by purchase price, the ranking changes slightly. Rank Device Price 30 GB write Write speed per £ 1 OSCOO SU001 £39.99 442 MB/s 11.05 MB/s per £ 2 Integral Turbo-C £49.95 509 MB/s 10.19 MB/s per £ 3 SanDisk Ultra Dual Drive Go £21.85 71 MB/s 3.26 MB/s per £ 4 Generic USB Stick £9.99 27 MB/s 2.74 MB/s per £ 5 SanDisk Ultra Flair £15.08 33 MB/s 2.22 MB/s per £ 6 Kingston Exodia £8.48 14 MB/s 1.64 MB/s per £ 7 Amazon Basics USB-C and USB-A Dual Port £25.70 38 MB/s 1.50 MB/s per £ 8 Amazon Basics 128GB USB 3.1 £21.49 30 MB/s 1.39 MB/s per £ 9 Kingston Kyson £16.00 21 MB/s 1.29 MB/s per £ 10 Kingston DT70 £12.65 15 MB/s 1.16 MB/s per £ 11 Kingston G4 £13.58 12 MB/s 0.87 MB/s per £ The OSCOO SU001 remains the best value when using its highest measured sustained write result, achieving 11.05 MB/s per £. The Integral Turbo-C remains close behind at 10.19 MB/s per £, while also delivering the fastest absolute performance in the test. The USB-A adapter result shows that it remains a high-performance option even when Thunderbolt 4 is not available. Initial awards Fastest overall Integral USSD 256GB Turbo-C USB 3.2 Gen 2x2 The Integral was the fastest device in the test by a large margin. It reached 2043 MB/s read and 509 MB/s sustained write through the Thunderbolt 4 port. It is the best option where maximum transfer speed is the priority. Thunderbolt 4 delivered the highest read speed, but the USB-C and USB-A adapter results also remained very strong, both exceeding 460 MB/s sustained write in the 30 GiB test. Best all-round USB drive Integral USSD 256GB Turbo-C USB 3.2 Gen 2x2 The Integral was the fastest device in the test by a large margin. It reached 2043 MB/s read and 509 MB/s sustained write through the Thunderbolt 4 port, making it the clear performance leader. It also performed strongly over USB-C and through the USB-A adapter, with both connections delivering more than 460 MB/s sustained write in the 30 GB test. Thunderbolt 4 provided the highest read speed, but the Integral remained a very fast option across all tested connection types. Best conventional dual-connector flash drive SanDisk Ultra Dual Drive Go The SanDisk Ultra Dual Drive Go was much slower than the Integral and OSCOO on sustained writes, but it performed consistently across USB-A, USB-C, and Thunderbolt 4 ports. Its read speed was close to the supplied advertised figure, making it a useful read-focused deployment or support drive. Best budget surprise Generic USB Stick 64GB USB 3.0 The generic USB stick exceeded its supplied advertised figures and outperformed several branded budget drives in sustained write speed. It was not fast, but for its price it performed better than expected. Most consistent against advertised write speed Amazon Basics 128GB USB 3.1 The Amazon Basics 128GB USB 3.1 drive measured 29.92 MB/s against a supplied advertised write speed of 30 MB/s. It was not a fast device, but it was one of the few drives where the sustained write result closely matched the supplied write figure. Weakest result Kingston DataTraveler G4 The Kingston G4 had the slowest sustained 30 GB write time and the weakest mixed-file write performance. It completed the 30 GB write at 11.82 MB/s and the mixed-file write at only 2.75 MB/s. For a Windows deployment USB, this level of write performance would be a significant limitation. Conclusion The final results show that USB flash drive performance cannot be judged by capacity, connector type, or USB version alone. Several drives achieved strong read speeds, and many came close to or exceeded the supplied advertised read figures. Sustained write performance was far less predictable. Some devices dropped sharply when moving from the 5 GB ISO test to the 30 GB sustained write test, suggesting that short-burst performance can give a misleading impression of real-world behaviour. The Integral Turbo-C is the fastest device tested, but the OSCOO SU001 is the strongest all-round result when consistency, sustained write speed, connector flexibility, and price are considered together. The main practical finding is simple: two USB drives with similar packaging and USB version labels can behave completely differently. In this test, the difference between the fastest and slowest 30 GB write result was more than 40 times.
- PowerShell QuickEdit, Why It Wrecks Scripts
My scripting language of choice is PowerShell. For my sins, and there are plenty, I am a Microsoft engineer. Life choices were made, clearly not all of them wise. Still, PowerShell is built into Windows. Then there is PowerShell's QuickEdit. QuickEdit is one of those “helpful” Windows console features that sounds useful right up until it parks a Tank on top of your deployment script. It lets you interact with a running PowerShell window, select text directly from the console, and copy it. Wonderful. Marvellous. Exactly what nobody asked for during an unattended build. The problem is that when QuickEdit decides text is being selected, PowerShell stops dead in its tracks. Just stop. Your carefully written script, which may be installing applications, configuring Windows, applying security settings, or doing several hours of build work, suddenly sits there doing absolutely nothing. And the trigger? A mouse click. A tiny drag. A badly timed brush across the console window. Sometimes, it feels like the mouse only has to think about PowerShell from across the room and that is enough. The script freezes, the build stalls, and you are left wondering why it's taking so long to complete I could almost forgive it if QuickEdit only caused problems when I deliberately selected text. But no, that would be far too reasonable. Instead, it has a special gift for interrupting scripts at exactly the wrong moment, when there's deadlines. And breathe... And then there's Turning QuickEdit Off QuickEdit can be turned off from the PowerShell window properties, but in true Microsoft fashion, changing the setting does not always mean the console window currently open will pay the slightest bit of attention. That would be too easy. Registry changes are not always picked up by an already running console, because apparently asking Windows to apply the setting you just changed to the thing you are actually using is an unreasonable expectation. Somewhere deep inside the operating system, a committee clearly decided that the correct behaviour was “yes, we have accepted your change, no, we will not be using it yet.” So, for reliable behaviour, close PowerShell and reopen it, because naturally even turning off the thing that randomly pauses your scripts requires the traditional Microsoft ritual of shutting it down and starting it again. And breathe... (again)... Clearly, I'm having a bad Microsoft day. Where QuickEdit Is Configured QuickEdit settings are stored in the user console registry settings. The main location is: HKCU:\Console PowerShell also has per-application console settings under: HKCU:\Console\%SystemRoot%_System32_WindowsPowerShell_v1.0_powershell.exe HKCU:\Console\%SystemRoot%_SysWOW64_WindowsPowerShell_v1.0_powershell.exe The values used are: QuickEdit = 0 InsertMode = 0 Setting QuickEdit to 0 disables QuickEdit.Setting InsertMode to 0 keeps console input behaviour predictable. Disable QuickEdit for the Current User This function disables QuickEdit for the currently logged-on user. Function Disable-ConsoleQuickEdit { Set-ItemProperty -Path 'HKCU:\Console' -Name 'QuickEdit' -Value 0x00000000 -Force Set-ItemProperty -Path 'HKCU:\Console' -Name 'InsertMode' -Value 0x00000000 -Force Set-ItemProperty -Path 'HKCU:\Console\%SystemRoot%_System32_WindowsPowerShell_v1.0_powershell.exe' -Name 'QuickEdit' -Value 0 -Force Set-ItemProperty -Path 'HKCU:\Console\%SystemRoot%_System32_WindowsPowerShell_v1.0_powershell.exe' -Name 'InsertMode' -Value 0 -Force Set-ItemProperty -Path 'HKCU:\Console\%SystemRoot%_SysWOW64_WindowsPowerShell_v1.0_powershell.exe' -Name 'QuickEdit' -Value 0 -Force Set-ItemProperty -Path 'HKCU:\Console\%SystemRoot%_SysWOW64_WindowsPowerShell_v1.0_powershell.exe' -Name 'InsertMode' -Value 0 -Force } This only affects the current user profile. Disable QuickEdit for New Users To apply the same setting to new user profiles, update the Default User registry hive. Function DefaultUser-ConsoleQuickEdit { & REG LOAD HKLM\DEFAULT C:\Users\Default\NTUSER.DAT $RegistrySettings = @( @{ RelativePath = "Console"; Name = "QuickEdit"; Value = 0; Type = "DWord" }, @{ RelativePath = "Console"; Name = "InsertMode"; Value = 0; Type = "DWord" }, @{ RelativePath = "Console\%SystemRoot%_System32_WindowsPowerShell_v1.0_powershell.exe"; Name = "QuickEdit"; Value = 0; Type = "DWord" }, @{ RelativePath = "Console\%SystemRoot%_System32_WindowsPowerShell_v1.0_powershell.exe"; Name = "InsertMode"; Value = 0; Type = "DWord" }, @{ RelativePath = "Console\%SystemRoot%_SysWOW64_WindowsPowerShell_v1.0_powershell.exe"; Name = "QuickEdit"; Value = 0; Type = "DWord" }, @{ RelativePath = "Console\%SystemRoot%_SysWOW64_WindowsPowerShell_v1.0_powershell.exe"; Name = "InsertMode"; Value = 0; Type = "DWord" } ) foreach ($Setting in $RegistrySettings) { $FullPath = "HKLM:\DEFAULT\$($Setting.RelativePath)" if (!(Test-Path $FullPath)) { $Item = New-Item -Path $FullPath -Force if ($Item -and $Item.Handle) { $Item.Handle.Close() } } New-ItemProperty -Path $FullPath -Name $Setting.Name -PropertyType $Setting.Type -Value $Setting.Value -Force | Out-Null } [gc]::Collect() [gc]::WaitForPendingFinalizers() & REG UNLOAD HKLM\DEFAULT } This affects users created after the change is made. Disable QuickEdit for Administrator Before First Logon If the local Administrator profile exists but has not been used yet, the same settings can be written directly into its profile hive. Function Administrator-ConsoleQuickEdit { try { Get-ChildItem C:\Users\Administrator\NTUSER.DAT -ErrorAction Stop & REG LOAD HKLM\DEFAULT C:\Users\Administrator\NTUSER.DAT $RegistrySettings = @( @{ RelativePath = "Console"; Name = "QuickEdit"; Value = 0; Type = "DWord" }, @{ RelativePath = "Console"; Name = "InsertMode"; Value = 0; Type = "DWord" }, @{ RelativePath = "Console\%SystemRoot%_System32_WindowsPowerShell_v1.0_powershell.exe"; Name = "QuickEdit"; Value = 0; Type = "DWord" }, @{ RelativePath = "Console\%SystemRoot%_System32_WindowsPowerShell_v1.0_powershell.exe"; Name = "InsertMode"; Value = 0; Type = "DWord" }, @{ RelativePath = "Console\%SystemRoot%_SysWOW64_WindowsPowerShell_v1.0_powershell.exe"; Name = "QuickEdit"; Value = 0; Type = "DWord" }, @{ RelativePath = "Console\%SystemRoot%_SysWOW64_WindowsPowerShell_v1.0_powershell.exe"; Name = "InsertMode"; Value = 0; Type = "DWord" } ) foreach ($Setting in $RegistrySettings) { $FullPath = "HKLM:\DEFAULT\$($Setting.RelativePath)" if (!(Test-Path $FullPath)) { $Item = New-Item -Path $FullPath -Force if ($Item -and $Item.Handle) { $Item.Handle.Close() } } New-ItemProperty -Path $FullPath -Name $Setting.Name -PropertyType $Setting.Type -Value $Setting.Value -Force | Out-Null } [gc]::Collect() [gc]::WaitForPendingFinalizers() & REG UNLOAD HKLM\DEFAULT } catch { } } This is useful in build environments where the Administrator account may be enabled or used later. Recommended Usage For deployment scripts or image builds, QuickEdit should be disabled early in the process. Disable-ConsoleQuickEdit DefaultUser-ConsoleQuickEdit Administrator-ConsoleQuickEdit This covers the current user, future users, and the local Administrator profile where it exists. Final Thoughts QuickEdit is useful for manual console work, but it is not ideal for automation. A single accidental click inside the console window can make a script appear to hang. For build scripts, deployment scripts, and first boot configuration, that risk is not worth keeping. Disabling QuickEdit makes PowerShell-based automation more predictable, which is exactly what you want during a build or deployment.
- PowerShell ISE Dark Theme, Half Finished, Half Broken, Very Microsoft
ISE Dark Theme, Scuppered by Microsoft PowerShell ISE has a dark theme, let's face it, it's sub-optimal, half-arsed and clearly created by Microsoft. Before anyone reaches for the pitchfork, yes, Visual Studio Code is the better editor. It has extensions, Git integration, better formatting, better search, better debugging, better everything really. However, PowerShell ISE wins in one very specific place, real Windows administration. It's installed by default on every Windows client and Server. There is absolutely no way on this round earth that VS Code will be installed on my Servers and Domain Controllers. I prefer a dark theme when coding or scripting, there's less glare and eye strain... and here Microsoft strikes again. Change the background to the Dark Theme and the editor looks better for about three seconds, right up until normal text renders a nice shade of black on a dark blue background, XML turns into a red and blue crime scene, and half your unattended install file becomes harder to read than Microsoft licensing. ISE does not treat all text the same. PowerShell code uses one set of token colours. Plain text uses another setting. XML has its own separate colour dictionary. That means setting a dark background is not enough. You also need to fix script tokens, plain editor text, console text, warning text, error text, and XML token colours. a text file an xml file Does no one at Microsoft test anything......... So I Fixed it myself with a Tenaka ISE Theme The Tenaka theme fixes the dark mode correctly.. I hope. It sets a dark blue editor background, off-white main text, bronze commands, yellow parameters, green variables, muted strings, blue type/member highlighting, and readable warning and error colours. The output window is dark blue with off-white text, errors are still red. It also fixes XML separately, which matters if you work with unattended install files, deployment XML, or configuration files. XML tags, attributes, quoted strings, comments, and plain command text inside XML are all given their own readable colours. How to Configure ISE Tenaka Theme Clearly dazzled by this frankly magnificent colour selection, you will obviously want to know how to apply the Tenaka Theme to PowerShell ISE. Default Profile This is easy, unless Constrained Language Mode is your thing, then skip this and go to importing a theme. Copy the script to the path below, inserting your username for Expand Script $DarkBlue = '#FF0B1F2A' # very dark blue $NearWhite = '#FFEAF4F8' # near white $Bronze = '#FFD19047' # bronze/gold $Yellow = '#FFFFD166' # warm yellow $MutedText = '#FFB7CBD6' # muted grey/blue $BlueAccent = '#FF4FC3F7' # light blue $Green = '#FF7CFC98' # green $Copper = '#FFB56A2F' # darker bronze/copper $Red = '#FFFF6B6B' # red # WPF colour objects, required for XML token colours $DarkBlueColor = [System.Windows.Media.ColorConverter]::ConvertFromString($DarkBlue) $NearWhiteColor = [System.Windows.Media.ColorConverter]::ConvertFromString($NearWhite) $BronzeColor = [System.Windows.Media.ColorConverter]::ConvertFromString($Bronze) $YellowColor = [System.Windows.Media.ColorConverter]::ConvertFromString($Yellow) $MutedTextColor = [System.Windows.Media.ColorConverter]::ConvertFromString($MutedText) $BlueAccentColor = [System.Windows.Media.ColorConverter]::ConvertFromString($BlueAccent) $GreenColor = [System.Windows.Media.ColorConverter]::ConvertFromString($Green) # Main editor background and plain text $psISE.Options.ScriptPaneBackgroundColor = $DarkBlue $psISE.Options.ScriptPaneForegroundColor = $NearWhite # Console / output pane $psISE.Options.ConsolePaneBackgroundColor = $DarkBlue $psISE.Options.ConsolePaneTextBackgroundColor = $DarkBlue $psISE.Options.ConsolePaneForegroundColor = $MutedText # Normal / unknown text $psISE.Options.TokenColors.Item('Unknown') = $NearWhite # Commands and functions $psISE.Options.TokenColors.Item('Command') = $Bronze $psISE.Options.TokenColors.Item('CommandArgument') = $NearWhite # Parameters $psISE.Options.TokenColors.Item('CommandParameter') = $Yellow # PowerShell keywords $psISE.Options.TokenColors.Item('Keyword') = $Bronze # Variables $psISE.Options.TokenColors.Item('Variable') = $Green # Strings $psISE.Options.TokenColors.Item('String') = $MutedText # Numbers $psISE.Options.TokenColors.Item('Number') = $Yellow # Operators $psISE.Options.TokenColors.Item('Operator') = $NearWhite # Brackets, braces, parentheses, separators $psISE.Options.TokenColors.Item('GroupStart') = $NearWhite $psISE.Options.TokenColors.Item('GroupEnd') = $NearWhite $psISE.Options.TokenColors.Item('StatementSeparator') = $NearWhite $psISE.Options.TokenColors.Item('LineContinuation') = $NearWhite # Types, for example [string], [int], [System.IO.File] $psISE.Options.TokenColors.Item('Type') = $BlueAccent # Object members, for example .Name, .FullName $psISE.Options.TokenColors.Item('Member') = $BlueAccent # Comments $psISE.Options.TokenColors.Item('Comment') = $Copper # Errors $psISE.Options.ErrorForegroundColor = $Red $psISE.Options.ErrorBackgroundColor = $DarkBlue # Warnings $psISE.Options.WarningForegroundColor = $Yellow $psISE.Options.WarningBackgroundColor = $DarkBlue # XML syntax highlighting $psISE.Options.XmlTokenColors.Item('Text') = $NearWhiteColor $psISE.Options.XmlTokenColors.Item('ElementName') = $BronzeColor $psISE.Options.XmlTokenColors.Item('Attribute') = $YellowColor $psISE.Options.XmlTokenColors.Item('QuotedString') = $MutedTextColor $psISE.Options.XmlTokenColors.Item('Tag') = $BlueAccentColor $psISE.Options.XmlTokenColors.Item('Quote') = $MutedTextColor $psISE.Options.XmlTokenColors.Item('Comment') = $GreenColor $psISE.Options.XmlTokenColors.Item('CommentDelimiter') = $GreenColor $psISE.Options.XmlTokenColors.Item('CharacterData') = $NearWhiteColor $psISE.Options.XmlTokenColors.Item('MarkupExtension') = $BlueAccentColor C:\Users\\Documents\WindowsPowerShell\Microsoft.PowerShellISE_profile.ps1 Restart ISE Creating a Theme Alternatively, paste the script into PowerShell ISE and run it with F8. This applies the Tenaka Theme for the current session. To save it as a reusable theme, open: Tools > Options > Manage Themes > Export When exporting, keep the original file extension, " .StorableColorTheme.ps1xml ". If the extension is changed, ISE won't recognise the file and the theme will not import later. Here's the link to Github Enjoy. And obviously, if you think you have a better colour scheme, you are free to be wrong, but do drop a comment in the box below. The Tenaka Theme is, quite clearly, the best ISE theme everrrrrrrr. Links: https://github.com/Tenaka/ISEColour
- Inside IR35, the 55% Tax Rate, so much for fairness.
How can HMRC take more in tax than an inside-IR35 contractor takes home, while still claiming the system is fair, when outside-IR35 contractors and employees already pay broadly similar amounts of tax? Let me explain..... HMRC says the off-payroll rules make sure a worker pays “broadly the same Income Tax and National Insurance as an employee would”. That is the official line. The difficulty is that this only describes part of what happens. A direct employee is paid through PAYE. Their wages are not subject to VAT, because services provided by an employee to their employer are outside the scope of VAT. An outside-IR35 contractor works through a company. That company pays corporation tax on profit, and the individual then pays dividend tax when profits are taken out. So outside IR35 is not tax free. HMRC still takes a substantial amount through company tax and personal tax. An inside-IR35 contractor is usually paid through an umbrella or other intermediary. HMRC’s umbrella guidance and its key information example show that the assignment rate is first reduced by employer costs such as employer National Insurance, Apprenticeship Levy, holiday pay arrangements, pension costs and umbrella margin before gross pay is calculated. PAYE and employee National Insurance are then deducted from what is left. Outside IR35 already carries a real tax burden The outside contractor does not sit outside the tax system. The company pays corporation tax, and the contractor then pays dividend tax on distributions. HMRC’s published rates also confirm the personal allowance taper above £100,000, which increases the effective marginal rate in that band. So the case for IR35 should not be based on the idea that outside contractors pay little or nothing. They do pay tax. The difference is that they are running a business and carrying business risk. They have to cover gaps between contracts, buy equipment, pay for training, fund pensions, and manage their own contingency. Inside IR35 creates a different problem Inside IR35 is often described as putting the contractor on a similar footing to an employee. In tax terms, that is only partly true. A real employee gets PAYE treatment, but they also get an actual employment relationship. That may include continuity, notice, redundancy protection, employer benefits, a pension contribution, and internal support for training and development. The inside contractor normally does not get that package. They are still temporary, they still face the risk of contracts ending, and they still have to think about pension, training and downtime themselves. At the same time, HMRC’s umbrella model means the assignment rate is reduced by employer-side costs before the worker even gets to gross pay. That is why many contractors see inside IR35 as worse than either of the cleaner models. It is not the same as being a normal employee, and it is not the same as running a business. Why inside rates rise in the real world If an outside contractor is pushed inside IR35, their take-home usually falls unless the day rate rises. That is because the inside structure is carrying more tax liabilities. Employer-side costs come out of the assignment rate first, then PAYE and employee NIC are taken from gross pay. HMRC’s umbrella example shows that clearly. That is why inside roles often command a higher day rate in practice. The rate increase is not because the work changed. It is because the tax and payment structure changed. Once the rate rises, the VAT on the labour supply rises as well, because supplies of staff are normally standard-rated for VAT. That is different from a direct employee, where wages are outside the scope of VAT. The VAT issue This is where the comparison with direct employment becomes awkward. A direct employee has no VAT on wages. HMRC is clear on that. A supply of staff is normally subject to VAT at the standard rate. HMRC is clear on that too. So an inside contractor can end up in a position where HMRC says they should be taxed broadly like an employee, but the labour still sits in a VATable supply chain that a real employee would never be in. That means the worker can face employment-style tax treatment while the client is still paying VAT on the labour supply. That is not the same as saying wages themselves are subject to VAT. They are not. The point is that the inside structure can still make the engagement more expensive than direct employment because the labour is being supplied through a VATable chain. Worked comparison Take a role with a market value of £600 a day over 220 days. That gives a base annual labour value of £132,000. Using current HMRC rates and umbrella mechanics, the broad picture looks like this: Model Basis Client cash cost incl. VAT VAT in client cost Approx. HMRC take, excl. VAT Approx. HMRC take, incl. VAT in chain Approx. net to worker Direct employee Employer cost equivalent to £600/day £132,000 £0 ~£57,600 ~£57,600 ~£74,400 Outside IR35 £600/day via PSC £158,400 £26,400 ~£50,500 ~£76,900 ~£81,500 Inside IR35, no uplift £600/day via umbrella £158,400 £26,400 ~£56,900 ~£83,300 ~£73,800 Inside IR35, 20% uplift £720/day via umbrella £190,080 £31,680 ~£72,900 ~ £104,600 ~£84,200 These figures are illustrative, not personal tax advice, but they reflect the structure shown in HMRC’s PAYE, NIC, VAT and umbrella guidance. The table shows four things. First, outside IR35 already produces a meaningful tax take. It is not a low-tax model in any ordinary sense. Second, inside IR35 at the same headline day rate can leave the worker with less than outside IR35, because the assignment rate is reduced before PAYE even starts. Third, if the inside contractor pushes for a higher rate to restore take-home, the client cost rises and the VAT rises with it. Fourth, inside IR35 can leave HMRC taking more than the worker takes home, once the full tax take in the chain is counted. Using the figures in the table: For inside IR35 at £600 a day with no uplift, the worker takes home about £73,800, while HMRC takes about £83,300. That means HMRC takes roughly £9,500 more than the contractor keeps. Put another way, HMRC takes about 53% of the combined tax-plus-take-home total, leaving the worker with only 47%. For inside IR35 at £720 a day with a 20% uplift, the worker takes home about £84,200, while HMRC takes about £104,600. That means HMRC takes roughly £20,400 more than the contractor keeps. In percentage terms, HMRC takes about 55% of the combined total, leaving the worker with only 45%. Let that sink in 55% tax rate for inside IR35 Contractor, with no rights, no expenses, no job security, or pension. Mobility and expenses This also affects where people are willing to work. Since April 2016, tax relief for home-to-work travel and subsistence has been restricted for workers providing personal services through an employment intermediary, such as an umbrella company, where they are subject to supervision, direction or control, or the right of it. HMRC’s Employment Status Manual sets this out, and the policy paper behind the change explains that the aim was to stop workers engaged through intermediaries claiming relief that direct employees could not usually claim. In practice, that means an inside contractor working away from home is often in a much weaker position on travel and subsistence than an outside contractor running a genuine business. Train fares, fuel, hotels and meals can stop being tax-efficient business expenses in the way many contractors expect. At the same time, the income funding those costs has already been pushed through PAYE and employee NIC, with employer-side costs already taken out of the assignment rate upstream. That changes behaviour. If a role is two hundred miles away and the worker cannot sensibly offset travel and accommodation costs, the role becomes much less attractive unless the rate goes up. So inside IR35 does not just affect take-home pay, it also makes it harder to take on work that involves travel or working away from home. Training, skills and self-funding A contractor normally funds a lot of their own development. That includes exams, courses, lab equipment, cloud spend, software, books and time between contracts. Outside IR35, the contractor is at least operating through a business structure that allows them to plan around that. Inside IR35 leaves less room. If more of the contract value is lost to employer-side costs, PAYE and NIC before the worker sees the money, there is less available for training, pension saving and contingency. That does not show up neatly on a tax table, but it does affect how easy it is for someone to keep their skills current. The main point If you only compare PAYE and NIC, you can say inside IR35 looks closer to employment. If you look at the whole position, including VAT on staff supply, the loss of deductible travel in many intermediary arrangements, the reduction in take-home at the same headline rate, and the lack of direct employee protections, the position is much less tidy. Outside IR35 already pays tax. Direct employment has no VAT on wages and comes with an actual employment relationship. Inside IR35 can combine lower take-home, higher client cost, VAT in the supply chain, limited expenses for mobility, and less room for pension, training and contingency. That is why many people do not accept the simple line that it creates parity. It may move tax treatment in that direction, but the wider commercial and practical position is still not the same.
- PowerShell to .NET for Admins, A Practical Training Guide
This guide is about making the jump into .NET as an IT admin, namely me. I’ve reached the limits of what I can realistically achieve with just PowerShell and need to push further. To be clear, I’m not a developer, my developer friends will attest to this by reminding me that PowerShell isn't a proper programming language and I should stop messing around. For my sins, I’m a Microsoft professional wishing he had gone the Linux route, who hates repetitive manual work. So this blog presents a good opportunity to combine learning with sharing the knowledge as I go. Sit back and enjoy. Cmdlets are clean, readable, and designed for common admin work. The problem is that sooner or later you hit the edge of what cmdlets expose. That is where .NET starts to matter. PowerShell is built on .NET. That means the objects you work with, the methods you call, the data types you manipulate, and a lot of the platform features underneath are all .NET based. This blog is aimed at someone who already knows the basics of PowerShell, variables, loops, functions, pipelines, and object properties, and wants to move into using .NET classes directly from the shell. The goal is not to turn you into a C# developer, I wouldn't know where to start. The goal is to make you far more effective in PowerShell. I know this is my goal, and for too long I've limited myself by what Cmdlets are available. Why .NET matters in PowerShell PowerShell works with objects, and those objects are usually .NET objects. When you run a cmdlet, the result is often an instance of a .NET class. When you manipulate a string, date, file, certificate, IP address, event log entry, XML document, or web response, you are usually handling a .NET type. Cmdlets vs .NET The first mental change is this: A cmdlet is a PowerShell command. A .NET class is a type. That is a cmdlet: Get-Date This is a .NET type: [System.DateTime] The cmdlet returns an object of that type: (Get-Date).GetType().FullName Expected output: System.DateTime So when you run Get-Date, you are not getting some vague shell output. You are getting a real .NET object. What you actually need to learn You do not need to learn all of .NET. That would be daft. What you need to learn is how to: identify the type of an object inspect its properties and methods understand static vs instance members create new .NET objects find available classes and methods use those classes safely in real admin work Find the real Type of an object $obj.GetType().FullName Example: $date = Get-Date $date.GetType().FullName That tells you exactly what you are dealing with. 4.2 Inspect an object $obj | Get-Member Example: $date | Get-Member This shows the objects: methods properties type name Show only methods $date | Get-Member -MemberType Method Show only properties $date | Get-Member -MemberType Property Inspect a .NET class directly If you already know the class name: [System.IO.File] | Get-Member -Static That shows the static methods and properties on the class. Show constructors [System.Text.StringBuilder].GetConstructors() Show method overloads [System.IO.File].GetMethods() | Where-Object Name -eq 'ReadAllText' That helps when the same method has multiple forms. Static members vs instance members This is one of the biggest early stumbling blocks. Static member Belongs to the class itself. You call it with : Example: [System.DateTime]::Now [System.IO.File]::Exists('C:\Temp\Test.txt') You do not need to create an object first. Instance member Belongs to an existing object. You call it with : Example: $date = Get-Date $date.AddDays(7) $date.ToString('yyyy-MM-dd') Quick rule: [TypeName]::Something means class level, static $object.Something means object level, instance How to create .NET objects There are two common ways: Old style $sb = New-Object System.Text.StringBuilder Cleaner modern style $sb = [System.Text.StringBuilder]::new() That second form is usually better, example: $sb = [System.Text.StringBuilder]::new() [void]$sb.Append('Server ') [void]$sb.Append('Report') $sb.ToString() The most useful .NET areas for admins You can go far just by learning a few namespaces: System.IO , Files, directories, paths, streams. System.DateTime , Dates, times, formatting, calculations. System.Net , DNS, IP addresses, sockets, HTTP related types. System.Text, Encoding , string handling, string builders. System.Security .Cryptography , Hashing, encryption, certificates, randomness. System.Xml , XML document parsing and manipulation. System.Diagnostics , Processes, event logs, performance details. System.Collections , Lists, dictionaries, queues, stacks, custom collections. Training module 1, understanding objects properly Before touching anything more advanced, make sure this part is solid. Exercise 1.1, inspect common objects Run each of these and check the type: (Get-Date).GetType().FullName ('hello').GetType().FullName (1).GetType().FullName (Get-Process | Select-Object -First 1).GetType().FullName (Get-Service | Select-Object -First 1).GetType().FullName What to look for Notice that strings, integers, dates, process objects, and service objects are all real types. They are not just plain text. Exercise 1.2, inspect members Get-Date | Get-Member 'hello world' | Get-Member Get-Service | Select-Object -First 1 | Get-Member Goal Learn to spot: property names method names type name at the top Exercise 1.3, property vs method Try these: $date = Get-Date $date.Year $date.ToString('yyyy-MM-dd') Now deliberately try the wrong thing: $date.Year() You should get an error because Year is a property, not a method. Training module 2, using strings and text Strings are everywhere in admin work, logs, file names, event messages, command output, config content. PowerShell strings are .NET strings. Useful methods $text = 'Server01.contoso.local' $text.ToUpper() $text.ToLower() $text.Contains('contoso') $text.Replace('Server01','DC01') $text.Split('.') $text.StartsWith('Server') $text.EndsWith('local') Real admin example, normalise computer names Imagine you are reading a list of hostnames from a file and want to clean them up. $names = @( 'srv-app-01', 'SRV-DB-02', 'Srv-Web-03 ') $cleanNames = foreach ($name in $names){ $name.Trim().ToUpper() } $cleanNames Why .NET matters here Methods like .Trim(), .ToUpper(), .Replace(), and .Split() are .NET string methods. You use them constantly in real scripts. Exercise 2.1 Take this array: $servers = @( ' dc01.corp.local ', ' FS01.corp.local', 'sql01.CORP.LOCAL ' ) Produce output where each entry is: trimmed converted to lower case split so that only the short hostname remains Expected result: dc01 FS01 sql01 One way: foreach ($server in $servers){ $server.Trim().ToLower().Split('.')[0] } Training module 3, working with DateTime Dates are a constant source of bad scripts. .NET helps make them predictable. Common examples [datetime]::Now [datetime]::UtcNow (Get-Date).AddDays(30) (Get-Date).AddHours(-4) (Get-Date).ToString('yyyy-MM-dd HH:mm:ss') Admin example, password expiry style reporting $today = [datetime]::Now $expiry = $today.AddDays(90) [pscustomobject]@{ Today = $today.ToString('yyyy-MM-dd') ExpiryDate = $expiry.ToString('yyyy-MM-dd') DaysRemaining = ($expiry - $today).Days } Admin example, file age check $path = 'C:\Temp\backup.zip' if (Test-Path $path) { $file = Get-Item $path $age = ([datetime]::file.LastWriteTime).TotalDays [pscustomobject]@{ Path = $file.FullName LastWriteTime = $file.LastWriteTime AgeDays = [math]::Round($age,2) } } Exercise 3.1 Write a small block that: gets the current time adds 14 days formats both dates as dd/MM/yyyy shows how many days are between them Trainin g module 4, files, folders, and paths with System.IO This is one of the best entry points into .NET for admin work. Common file methods [System.IO.File]::Exists('C:\Temp\demo.txt') [System.IO.File]::ReadAllText('C:\Temp\demo.txt') [System.IO.File]::WriteAllText('C:\Temp\demo.txt','hello') [System.IO.File]::AppendAllText('C:\Temp\demo.txt',"`r`nnew line") [System.IO.File]::Delete('C:\Temp\demo.txt') Common directory methods [System.IO.Directory]::Exists('C:\Temp') [System.IO.Directory]::CreateDirectory('C:\Temp\Logs') [System.IO.Directory]::GetFiles('C:\Temp') [System.IO.Directory]::GetDirectories('C:\Temp') Common path methods [System.IO.Path]::GetFileName('C:\Temp\demo.txt') [System.IO.Path]::GetExtension('C:\Temp\demo.txt') [System.IO.Path]::GetDirectoryName('C:\Temp\demo.txt') [System.IO.Path]::Combine('C:\Temp','Reports','report.txt') Real admin example, safe report path creation $basePath = 'C:\AdminReports' $dateFolder = (Get-Date).ToString('yyyyMMdd') $targetFolder = [System.IO.Path]::Combine($basePath, $dateFolder) if (-not [System.IO.Directory]::Exists($targetFolder)) { [void][System.IO.Directory]::CreateDirectory($targetFolder) } $reportPath = [System.IO.Path]::Combine($targetFolder, 'services.txt') $reportPath This avoids crude string concatenation like: "C:\AdminReports\" + $dateFolder + "\services.txt" That works until it does not. Admin example, read a config file in one go $configPath = 'C:\Scripts\appsettings.json' if ([System.IO.File]::Exists($configPath)) { $json = [System.IO.File]::ReadAllText($configPath) $config = $json | ConvertFrom-Json $config } Exercise 4.1 Create a script block that: creates C:\Temp\AdminLab if it does not exist creates a file called serverlist.txt writes three server names to it reads the file back and displays the content Exercise 4.2 Take the path: 'C:\Logs\IIS\W3SVC1\u_ex260328.log' Use System.IO. Path methods to extract: file name extension parent folder Training module 5, DNS and networking with System.Net There are PowerShell cmdlets for some network tasks, but .NET often gives you more direct access. Examples [System.Net.Dns]::GetHostName() [System.Net.Dns]::GetHostAddresses('localhost') [System.Net.Dns]::GetHostEntry('www.bbc.co.uk') Admin example, resolve multiple hosts $hosts = 'localhost','dc01','fileserver' foreach ($host in $hosts) { try { $addresses = [System.Net.Dns]::GetHostAddresses($host) foreach ($address in $addresses) { [pscustomobject]@{ Hostname = $host Address = $address.IPAddressToString Family = $address.AddressFamily } } } catch { [pscustomobject]@{ Hostname = $host Address = 'Resolution failed' Family = $null } } } Admin example, IP address validation $testIPs = '192.168.1.10','10.10.10.999','fe80::1','badvalue' foreach ($ip in $testIPs) { $parsed = $null $valid = [System.Net.IPAddress]::TryParse($ip, [ref]$parsed) [pscustomobject]@{ Input = $ip IsValid = $valid ParsedIP = if ($valid) { $parsed.IPAddressToString } else { $null} } } Exercise 5.1 Build a small resolver that takes an array of names and returns: hostname IP address whether the address is IPv4 or IPv6 Training module 6, working with event logs and diagnostics There are cmdlets for event logs, but understanding the .NET side is useful too. One simple example is using System.Diagnostics. Process example [System.Diagnostics.Process]::GetProcesses() | Select-Object -First 5 ProcessName, Id, WorkingSet64 Admin example, show high memory processes [System.Diagnostics.Process]::GetProcesses() | Sort-Object WorkingSet64 -Descending | Select-Object -First 10 @{ Name = 'ProcessName' Expression = { $_.ProcessName } }, @{ Name = 'PID' Expression = { $_.Id } }, @{ Name = 'MemoryMB' Expression = { [math]::Round($_.WorkingSet64 / 1MB, 2) } } Why it matters Even when PowerShell has a cmdlet like Get-Process, the underlying objects and properties are still .NET. Knowing that helps when exploring what is available. Exercise 6.1 Use Get-Process | Select-Object -First 1 | Get-Member and compare it to: [System.Diagnostics.Process] | Get-Member -Static Notice the difference between a process object and the class itself. Training module 7, collections and lists Arrays in PowerShell are fine for many tasks, but .NET collections are often better when you are building larger datasets or adding lots of items. Example - List $list = [System.Collections.Generic.List[string]]::new() $list.Add('DC01') $list.Add('FS01') $list.Add('SQL01') $list Why use it A PowerShell array gets recreated each time you add with +=, which is clumsy and less efficient for large loops. Admin example, collecting results in a scan $results = [System.Collections.Generic.List[object]]::new() Get-Service | ForEach-Object { $results.Add([pscustomobject]@{ Name = $_.Name Status = $_.Status }) } $results | Select-Object -First 5 Exercise 7.1 Rewrite a loop that uses $array += ... so that it uses List[object] instead. Training module 8, StringBuilder for report generation Admins often generate large text blobs, log files, config files, HTML fragments, CSV content, or report text. Using repeated string concatenation works, but it gets ugly and can be slow in bigger loops. System.Text.StringBuilder is built for that. Example $sb = [System.Text.StringBuilder]::new() [void]$sb.AppendLine('Service Report') [void]$sb.AppendLine('==============') Get-Service | Select-Object -First 5 | ForEach-Object { [void]$sb.AppendLine("Name: $($_.Name), Status: $($_.Status)") } $sb.ToString() Admin example, build a plain text server health summary $sb = [System.Text.StringBuilder]::new() [void]$sb.AppendLine("Health Check Report") [void]$sb.AppendLine("Generated: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')") [void]$sb.AppendLine("Server: $env:COMPUTERNAME") [void]$sb.AppendLine('') $os = Get-CimInstance Win32_OperatingSystem [void]$sb.AppendLine("OS: $($os.Caption)") [void]$sb.AppendLine("Version: $($os.Version)") [void]$sb.AppendLine("Last Boot: $($os.LastBootUpTime)") [void]$sb.AppendLine('') Get-Service | Where-Object Status -ne 'Running' | Select-Object -First 10 | ForEach-Object { [void]$sb.AppendLine("Service issue: $($_.Name), Status: $($_.Status)")} $report = $sb.ToString() $report Exercise 8.1 Use StringBuilder to create a report that contains: current hostname current date top 5 stopped services current PowerShell version Training module 9, hashing and cryptography basics You do not need to become a crypto engineer, but hashes are common in admin and security work. Admin example, compute a SHA256 hash of a file $path = 'C:\Temp\installer.msi' if ([System.IO.File]::Exists($path)) { $stream = [System.IO.File]::OpenRead($path) try { $sha256 = [System.Security.Cryptography.SHA256]::Create() try { $hashBytes = $sha256.ComputeHash($stream) $hashString = [System.BitConverter]::ToString($hashBytes).Replace('-','') $hashString } finally { $sha256.Dispose() } } finally { $stream.Dispose() } } Yes, PowerShell has Get-FileHash, and for most cases that is easier. The point here is to show that you can use the underlying .NET classes directly if needed. Exercise 9.1 Take a short text string, convert it to bytes with System.Text.Encoding, then hash it with SHA256. Hint: [System.Text.Encoding]::UTF8.GetBytes('hello world') Training module 10, XML and structured configuration Windows admin work is full of XML, event data, config files, policy exports, scheduled tasks, web.config, and more. PowerShell has the [xml] accelerator, which is tied to .NET XML classes. Example [xml]$xml = @" "@ $xml.Servers.Server $xml.Servers.Server[0].Name Admin example, parse a simple config [xml]$config = @" "@ [pscustomobject]@{ AppName = $config.Settings.App.Name Enabled = $config.Settings.App.Enabled Path = $config.Settings.Path.Value } Exercise 10.1 Create a small XML sample with three servers and extract: all names the first server any server with a chosen role Training module 11, finding available .NET classes This is the part many people mean when they ask, “How do I find the .NET commands?” Really, they mean classes, methods, constructors, and namespaces. Method 1, inspect objects you already have This is the best place to start. Get-Service | Select-Object -First 1 | Get-Member (Get-Date).GetType().FullName Method 2, inspect classes you know about [System.IO.File] | Get-Member -Static [System.Net.Dns] | Get-Member -Static Method 3, list loaded assemblies [AppDomain]::CurrentDomain.GetAssemblies() | Select-Object FullName, Location Method 4, list types from assemblies [AppDomain]::CurrentDomain.GetAssemblies() | ForEach-Object { $_.GetTypes() } | Select-Object FullName That can be noisy, so filter it: [AppDomain]::CurrentDomain.GetAssemblies() | ForEach-Object { $_.GetTypes() } | Where-Object FullName -like 'System.Net*' | Select-Object FullName Method 5, use tab completion Type part of a namespace and press Tab. [System.IO. or [System.Security.Cryptography. Exercise 11.1 Find and inspect: System.IO.Path System.Net.IPAddress System.Text.Encoding System.Environment For each one, identify at least two useful static members. Training module 12, method overloads and how to work them out A lot of .NET methods come in multiple forms. For example, ReadAllText might have overloads for different encodings. To see that: [System.IO.File].GetMethods() | Where-Object Name -eq 'ReadAllText' You can also inspect constructors this way: [System.IO.FileInfo].GetConstructors() Admin example, choosing the right overload Suppose you want to read a file using UTF8 explicitly. You would inspect the available overloads, then pick the one that accepts both a path and an encoding object. $content = [System.IO.File]::ReadAllText( 'C:\Temp\data.txt', [System.Text.Encoding]::UTF8) Exercise 12.1 Inspect WriteAllText overloads and write a file using UTF8 explicitly. Admin mini labs These are realistic tasks where .NET makes sense. Lab A, validate and normalise IP addresses Goal: take a mixed list of strings validate whether each is an IP address return clean output $values = @( '192.168.1.20', ' 10.0.0.5 ', 'bad-ip', 'fe80::1' ) foreach ($value in $values) { $ipObj = $null $trimmed = $value.Trim() $valid = [System.Net.IPAddress]::TryParse($trimmed, [ref]$ipObj) [pscustomobject]@{ Input = $value Normalised = if ($valid) { $ipObj.IPAddressToString } else { $null } IsValid = $valid Family = if ($valid) { $ipObj.AddressFamily } else { $null } } } Lab B, build a timestamped report path Goal: create a dated folder structure generate a report path safely $root = 'C:\Reports' $today = (Get-Date).ToString('yyyyMMdd') $server = $env:COMPUTERNAME $folder = [System.IO.Path]::Combine($root, $today) if (-not [System.IO.Directory]::Exists($folder)) { [void][System.IO.Directory]::CreateDirectory($folder) } $file = [System.IO.Path]::Combine($folder, "${server}_services.txt") [System.IO.File]::WriteAllText($file, "Service report for $server") $file Lab C, build a text report with StringBuilder Goal: avoid horrible string concatenation produce a clean report $sb = [System.Text.StringBuilder]::new() [void]$sb.AppendLine("Service Status Report") [void]$sb.AppendLine("Generated: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')") [void]$sb.AppendLine("Host: $env:COMPUTERNAME") [void]$sb.AppendLine('') Get-Service | Select-Object -First 10 | ForEach-Object { [void]$sb.AppendLine("$($_.Name), $($_.Status)")} $report = $sb.ToString() $report Lab D, hash a file and record the result Goal: compute a hash write it to a log file $filePath = 'C:\reports\file to hash.txt' $logPath = 'C:\reports\hashlog.txt' $logDir = Split-Path $logPath -Parent if (-not [System.IO.Directory]::Exists($logDir)) { [void][System.IO.Directory]::CreateDirectory($logDir) } if ([System.IO.File]::Exists($filePath)) { $stream = [System.IO.File]::OpenRead($filePath) try { $sha256 = [System.Security.Cryptography.SHA256]::Create() try { $hashBytes = $sha256.ComputeHash($stream) $hashString = [System.BitConverter]::ToString($hashBytes).Replace('-', '') [System.IO.File]::AppendAllText($logPath, "$filePath,$hashString`r`n") Write-Host "Hash written to $logPath" } finally { $sha256.Dispose() } } finally { $stream.Dispose() } } else { Write-Host "Source file not found: $filePath" } Common admin patterns where .NET is genuinely useful This is where .NET tends to be worth using instead of just sticking to cmdlets. Safer path handling Use: [System.IO.Path]::Combine() Instead of stitching file paths together badly. Better text generation Use: [System.Text.StringBuilder] For larger reports or file output. Validation of structured values Use: [System.Net.IPAddress]::TryParse()[datetime]::TryParse()[guid]::TryParse() Instead of flaky regex Precise file handling Use: [System.IO.File][System.IO.Directory] When you need exact behaviour. Lower-level feature access Sometimes there is no good cmdlet, or the cmdlet hides too much. That is where the raw class becomes useful. Common mistakes Treating everything as text Bad scripts convert useful objects into strings too early, then try to parse them back later. Work with the object as long as possible. Not checking the type first Always verify: $obj.GetType().FullName $obj | Get-Member Confusing class methods and object methods Wrong: $date = [System.DateTime] $date.AddDays(1) Right: [System.DateTime]::Now.AddDays(1) or $date = Get-Date $date.AddDays(1) Ignoring overloads If a method is not behaving the way you expect, inspect the overloads. There may be several versions. Using .NET where a cmdlet is clearer Do not become one of those people who rewrites obvious PowerShell into ugly pseudo C# for no reason. If Get-FileHash, Get-Content, Import-Csv, or Resolve-DnsName does the job cleanly, use it. The point is to know when .NET helps, not to show off. A sensible learning path If you want to build this skill properly, do it in this order. Stage 1, object fundamentals Practice: .GetType().FullName Get-Member property vs method Stage 2, useful types Practice with: strings DateTime System.IO.File System.IO.Path System.Net.Dns Stage 3, object creation Practice with: ::new() constructors StringBuilder generic lists Stage 4, discovery Practice with: .GetMethods() .GetConstructors() loaded assemblies filtered type discovery Stage 5, real scripts Use .NET deliberately in: path building file validation date calculations report generation value parsing and validation Quick reference cheat sheet What type is this? $obj.GetType().FullName What can it do? $obj | Get-Member What static members does this class have? [System.IO.File] | Get-Member -Static How do I create a new object? $obj = [System.Text.StringBuilder]::new() How do I inspect constructors? [System.Text.StringBuilder].GetConstructors() How do I inspect method overloads? [System.IO.File].GetMethods() | Where-Object Name -eq 'ReadAllText' How do I safely combine file paths? [System.IO.Path]::Combine('C:\Temp','Reports','output.txt') How do I validate an IP address? $ip = $null[System.Net.IPAddress]::TryParse('192.168.1.1',[ref]$ip) How do I get the current time in .NET? [datetime]::Now
- Hyper-V Killing WiFi Performance: When Your Virtual Switch Kills Your Upload Speed
There is a particularly annoying problem that can appear when running Hyper-V on laptops that depend on WiFi instead of proper Ethernet. If you use External and Internal switches like I do for moving VMs between Hyper-V servers and dev laptops, you may run into it sooner rather than later. Everything looks fine, the adapter reports healthy, signal is strong, drivers are current, yet performance completely implodes, turning a high speed WiFi into dial-up. Well, this teaches me for using Hyper-V. The root cause is the Hyper-V External Virtual Switch binding directly to the WiFi adapter. When this happens, Windows effectively inserts a virtual network filter between the physical adapter and the operating system. In theory this allows VMs to share the adapter cleanly. In practice, especially with Intel WiFi chipsets, it can absolutely destroy throughput, particularly upload performance. Normal performance without Hyper-V external binding When the Hyper-V switch is not attached to the WiFi adapter, performance is exactly what you would expect from a modern WiFi 6E connection: Download: 675 Mbps Upload: 104 Mbps Latency: 5 ms This shows the wireless adapter working normally with no Hyper-V networking overhead. Hyper-V switch configured as Internal Here the Hyper-V switch is configured as: Internal network This means: VMs can talk to the host No physical adapter binding exists WiFi operates normally Hyper-V networking stays isolated This is effectively the "safe" configuration for laptop labs. Performance collapse when External switch is enabled When the Hyper-V switch is changed to: External network → Intel Wi-Fi 6E AX211 Performance immediately drops: Download: 272 Mbps Upload: 0.14 Mbps Latency: 5 ms Yes, that upload number is correct. This is Hyper-V inserting the virtual switch filter driver into the WiFi stack and something in that path going very wrong. When uploads drop this dramatically while latency stays stable, you are usually looking at driver interaction problems rather than RF conditions. The actual problematic configuration External virtual switch bound to: Intel(R) Wi-Fi 6E AX211 160MHz Allow management operating system to share this network adapter: Hyper-V takes ownership of the physical adapter Creates a virtual NIC for Windows Routes host traffic through the Hyper-V switch Inserts filtering extensions into the datapath Effectively your host becomes just another VM from a networking perspective. That extra abstraction layer is where performance dies. What Hyper-V is actually doing to your WiFi When you create an External switch, Hyper-V installs: Hyper-V Extensible Virtual SwitchVirtual Switch Filtering PlatformNDIS filter drivers Your traffic path becomes: WiFi Adapter→ Hyper-V Virtual Switch→ Virtual NIC→ Windows TCP/IP stack Instead of: WiFi Adapter→ Windows TCP/IP stack That extra path introduces: Packet inspection overhead Driver compatibility issues Queue handling differences Offload conflicts Buffering changes Intel wireless drivers are particularly sensitive to this. Why upload gets hit hardest Uploads often break first because: WiFi contention handling is asymmetric Driver transmit queues get affected Large Send Offload interactions change behaviour Virtual switch buffering delays ACK flow This results in TCP throttling itself. The connection looks fine, but TCP thinks congestion exists. So it slows itself down. Practical fixes that actually work Avoid External switches on WiFi (best fix) The most reliable solution is simply: Do not bind Hyper-V External switches to WiFi adapters. Instead use: Default Switch (NAT) or Internal Switch + NAT These approaches avoid inserting the Hyper-V switching layer into the physical wireless path. Disable Large Send Offload (LSO) If you must use an External switch, disabling LSO can help stability. Location: Device Manager→ Network Adapter→ Properties→ Advanced Disable: Large Send Offload v2 (IPv4) Large Send Offload v2 (IPv6) Why this helps: LSO allows Windows to hand large packets to the NIC for segmentation. When Hyper-V filtering is involved, segmentation timing can become inefficient or buggy, particularly on wireless adapters. Disable Receive Segment Coalescing (RSC) RSC can also cause problems in virtual networking paths. Disable via PowerShell: Disable-NetAdapterRsc -Name "Wi-Fi" Why this helps: RSC combines packets to reduce CPU usage. Hyper-V filtering sometimes interferes with this process, causing throughput inconsistencies rather than improvements. VMQ is usually irrelevant for WiFi VMQ tuning is often suggested but usually does nothing for wireless adapters because VMQ is primarily designed for high speed Ethernet NICs. If tested, disabling can be done with: Set-VMNetworkAdapter -ManagementOS -Name "vEthernet (External)" -VmqWeight 0 But this should be considered a secondary tuning step, not a primary fix.
- AI Prompt Engineering for IT Pros Without Producing Slop
There is no shortage of nonsense written about AI prompt engineering. I am not claiming to be an expert, but I have spent time researching, testing, and working out what actually improves the output. AI, well acutally LLM, as it's not true AI, is now an invaluable assistant that doesn't charge by the hour or day. Prompt engineering is simply the skill of asking clearly, giving the right context, setting boundaries, and knowing what sort of result you actually want. That is it. No magic phrases. No hidden syntax. No secret incantation that turns a chatbot into an infallible architect, engineer, or security consultant. For technical people, that is actually good news. It means prompt engineering is not some ridiculous new profession. It is just the same engineering discipline you already use elsewhere, precision, structure, context, constraints, and validation. The basic rule Take this as a starting point. A weak prompt looks like this: How do I make an AI drone? That sounds reasonable until you notice what is missing. No audience, no scope, no safety boundary, no skill level, no budget, no structure, and no clue whether the answer is supposed to be a quick overview, a shopping list, a build guide, or a technical architecture. A better version looks like this: Explain how to build a hobbyist AI drone for a technical reader familiar with Linux, Python, Jetson or Raspberry Pi devices, and basic electronics. Focus on a safe build using a flight controller, companion computer, camera module, telemetry link, and onboard computer vision. Structure the answer as hardware, software, integration, testing, and safety considerations. Keep it practical and avoid hype. That is the heart of prompt engineering. Remove ambiguity, get better output. A simple structure that works Most useful prompts contain the same core parts, whether you are generating an article, reviewing code, analysing a screenshot, or extracting structured data. You need the task, the context, the constraints, the source material if there's any, and the output shape. In plain English, that usually means: What do you want done. Who is it for. What matters. What should be avoided. What the result should look like. A practical formula is: Act as a [role]. Create [output]. It is for [audience]. Use this context: [context].Requirements: [constraints]. Avoid: [exclusions].Output as: [format]. Practical prompt examples The easiest way to understand prompt engineering is to look at weak prompts beside stronger ones. The pattern becomes obvious very quickly. Explaining the basics Weak prompt: How do I make an AI drone? Better prompt: Explain how a hobbyist can build an AI drone for learning purposes. Assume the reader understands Linux, Python, and basic electronics but has never built an autonomous drone before. Cover the main components, how they fit together, and the difference between a normal drone and one with onboard AI functions. Keep it practical and readable. Why it works: It defines the reader, the scope, and the level of explanation. Writing a technical article Weak prompt: Write about AI drones. Better prompt: Write a technical article for engineers and advanced hobbyists on how to build an AI drone using a flight controller, companion computer, camera, and onboard vision model. Focus on real-world build choices, compute limits, latency, telemetry, control loops, and safety constraints. Keep it grounded, practical, and free of generic tech hype. Why it works: It narrows the topic and tells the model what matters. Creating a beginner build guide Weak prompt: Give me the steps to build an AI drone. Better prompt: Create a beginner-friendly build guide for a hobbyist AI drone. Assume the reader is comfortable with Linux and Python but is new to drone hardware. Cover frame, motors, ESCs, flight controller, battery, camera, companion computer, telemetry, and safe testing. Keep it simple and structured. Why it works: It asks for a guide, defines the starting point, and gives the answer a clear shape. Designing the system properly Weak prompt: Design an AI drone. Better prompt: Design a hobbyist AI drone using a standard flight controller for stabilisation and a separate companion computer for onboard AI processing. Explain the role of the ESCs, motors, GPS, IMU, camera, telemetry radio, battery, and onboard computer. Show how data flows between components and highlight likely issues with latency, power draw, and signal loss. Why it works: It turns a vague request into an actual architecture exercise. Working within a budget Weak prompt: Suggest parts for an AI drone. Better prompt: Write a build plan for an AI drone with a budget of £800 to £1,200. Prioritise stability, safe testing, and offline object detection rather than acrobatics or long-range flight. Use a conventional flight controller and a separate companion computer, such as a Jetson Nano. Output the answer as a bill of materials, architecture summary, software stack, test plan, and known limitations. Why it works: It adds budget, priorities, and a specific output format. Generating Python safely Weak prompt: Write Python for an AI drone. Better prompt: Write a Python 3 script for the Jetson Nano on a hobbyist AI drone that reads frames from a camera, performs basic object detection, and outputs structured telemetry events rather than directly controlling flight. Keep the code modular, readable, and safe for lab testing. Include error handling and log detection confidence, timestamp, and object class. Why it works: It defines what the code should do, what it should not do, and how it should behave. Reviewing PowerShell properly Weak prompt: Check this script. Better prompt: Review this PowerShell script as if you were validating an engineering or audit tool. Focus on logic errors, false positives, bad assumptions, output usefulness, and edge cases. Do not rewrite the whole script unless necessary. Show the most important issues first, then provide targeted fixes. Why it works: It asks for review rather than a random rewrite. Images and Screenshots Weak prompt: What is this screenshot? Better prompt: Analyse this screenshot and explain what the error likely means, what subsystem is involved, and what checks should be performed first. Do not just describe the image contents. Why it works: It asks for interpretation. Generating an image Weak prompt: Make an image of an AI drone. Better prompt: Create a wide technical blog banner in a dark, high-tech style. The subject is an AI drone operating in a hostile RF environment, with subtle references to computer vision, telemetry, navigation, and control link interference. Keep it sharp, serious, and minimal. Avoid toy-drone styling, cartoon clichés, and generic stock art. Why it works: It defines purpose, style, and what to avoid. Extracting structured data Weak prompt: Summarise this drone report. Better prompt: Read this report and extract the findings into JSON with fields for title, severity, affected_component, summary, evidence, and remediation. Keep the wording concise, preserve technical meaning, and do not invent missing details. If something is unclear, mark it as unknown rather than guessing. Why it works: It tells the model exactly how to shape the output. Asking for a serious long-form article Weak prompt: Write a good article about AI drones. Better prompt: Write a long-form technical article on how to build a hobbyist AI drone using a flight controller, companion computer, camera, and onboard processing. Keep the tone direct, practical, technical, and grounded in real-world constraints. Cover hardware selection, software stack, vision processing, telemetry, latency, power draw, testing, and safety controls. Include practical examples from beginner to advanced level. No sales language, no obvious AI phrasing, and no exaggerated claims about autonomy or intelligence. Why it works: It defines tone, scope, and the usual traps to avoid. How to validate output without reading every line This is where AI-assisted work either becomes efficient or turns into a complete faff. Cross Checking with Other Models: If the task matters, use another model to challenge the answer. ChatGPT, Gemini, and Claude often fail in different ways, which makes cross-checking genuinely useful. You may notice I don't reference Microsoft's CoPilot, and there's a perfectly no nonsense straight up answer, and in my opinion, it's sub-optimal and on par with Bing as a service when compared to Google. And yes, multiple models agreeing can increase confidence. It is still not proof. Treat it as confidence scoring, not truth. Validating Output yourself: If you're stuck with a particular model and need to validate every sentence manually, the time savings disappear. The answer is not to trust the output blindly. The answer is to validate more intelligently. Start with structure. Are the right sections present? Are the main risks surfaced? Does the answer actually match the task? Is anything suspiciously specific without evidence behind it? Make the model expose its own weak points. Ask for assumptions and uncertainty: List any assumptions you made, anything that may be uncertain, and anything that should be verified before this is used operationally. Ask for a self-critique: Review your own answer and identify the weakest parts, likely inaccuracies, and anything that sounds more confident than the evidence supports. A good review prompt for a second model is: Review the following technical answer for factual accuracy, omissions, hidden assumptions, and overconfident wording. Do not rewrite it yet. First identify anything that looks wrong, weak, or unsupported. When working from reports or documents, it also helps to force an evidence-first workflow: Extract the key evidence points first. Then group them by severity. Then write a summary based only on those points. For code, test behaviour, worry less about syntax. Run it with good input, bad input, and awkward edge cases. What it is actually good for Used properly, AI is good at drafting, rewriting, explaining, reviewing code, analysing screenshots, summarising reports, extracting structure from messy information, and turning rough notes into something usable. It is also genuinely helpful for project work. If I can drag myself away from work, I may actually have time to build the AI drone. It can refine build guides, explain component roles, review Python snippets for image processing, help draft test plans, and turn messy technical notes into documentation that a person can actually read. Final thoughts The better you define the task, the audience, the constraints, and the output shape, the better the result tends to be. The less guessing the model has to do, the less rubbish you have to clean up afterwards.
- LOLDrivers, why kernel drivers are the new attack surface
Why Old Signed and Legitimate are a Risk Old but legitimately signed drivers are dangerous because they provide a trusted execution path straight into the Windows kernel, where most modern security controls have no visibility. Once a vulnerable driver is loaded, an attacker can abuse known flaws to gain arbitrary kernel read and write access, disable security features, tamper with credential protections, and hide processes or files, all while appearing “trusted” because the driver is signed. This technique, commonly called BYOVD (Bring Your Own Vulnerable Driver), bypasses application control, kernel exploit mitigations, and many EDR hooks, allowing attackers to operate below the operating system’s security boundary. LOLDrivers.io to the Rescue LOLDrivers is a curated, continuously maintained catalogue of Windows drivers that have been abused, or are suitable for abuse, in real attacks. The list covers both malicious drivers and legitimate signed drivers with exploitable flaws. Visit LOLDrivers , its a great resource, its free, and the database of browserable drivers is awesome. The original goal was to embed an offline reference of the LOLDrivers dataset into the security tool I am building. Instead, I decided to release my own PowerShell implementation. This script creates a local, offline copy of the LOLDrivers database and provides clear, colour-coded output when a match is detected. What “Living Off the Land Drivers” actually means Living off the land usually refers to abusing built-in system tools. LOLDrivers applies the same concept to the kernel. Instead of dropping custom malware, attackers load a signed, trusted, but vulnerable driver and use it as a control interface into kernel memory. Once loaded, these drivers can be used to: Read and write arbitrary kernel memory Kill protected security processes Disable EDR hooks Bypass HVCI and protected process light (PPL) Load unsigned kernel code The risk categories in LOLDrivers LOLDrivers splits drivers into functional risk classes. This is important, because not all of them are malicious by design. Vulnerable but legitimate drivers These are signed drivers shipped by vendors that expose IOCTL handlers or memory primitives that can be abused. They are frequently used for: Arbitrary kernel read/write Token manipulation Security product termination Callback and hook removal These are the backbone of most BYOVD chains. Explicitly malicious drivers These drivers contain intentionally malicious functionality. They are often used as stealth rootkits or kernel loaders. Typical capabilities include: Process hiding File hiding Credential interception Persistence enforcement These drivers usually exist only to support a wider malware framework. Dual-use operational drivers “Dual-use” drivers are legitimate software for admins, OEMs, and hardware vendors, but dangerous in the wrong hands . These drivers are not exploits, but they expose kernel-level control surfaces that attackers can directly abuse. Typical capabilities include: Physical memory access MSR and PCI configuration Debug and hardware inspection features When abused, they provide the same primitives as a kernel exploit, without triggering exploit detection. Why Microsoft’s blocklist is not enough Microsoft maintains a kernel driver blocklist, but: It is reactive It is incomplete It does not cover every vulnerable version It is often bypassed by version pinning or re-signing What Needs to Be Done At a minimum, organisations must start treating driver loading as a high-risk security boundary, not a background system event. If you are not monitoring driver activity, you are blind to one of the most reliable attacker techniques in use today. The baseline controls should include: Integrate LOLDrivers intelligence into Sysmon, using the curated driver blocklist and metadata maintained by Magicsword.io , so that known vulnerable and malicious drivers can be detected at load time. Log every driver load event, not just failures. Silent, successful driver loads are how attackers bypass EDR and kernel protections. Continuously compare loaded drivers against the LOLDrivers dataset, both in real time and retrospectively, so newly classified drivers can be flagged even after they have already been seen in the environment. Alert based on abuse category, not just a hash match. A driver used for credential theft, EDR bypass, or kernel memory access represents a fundamentally different risk than a generic vulnerable driver, and should be triaged accordingly. Finally, credit where it's due Credit goes to the LOLDrivers project, without their work, this attack surface would remain largely undocumented, leaving defenders blind to a class of kernel-level abuse that is actively exploited. Show your support and visit their site.
- Quantum-Resistant Cryptography: Preparing for the Post-Quantum Era
A special thanks to 'D' for proofreading and providing valuable insights. With the rapid advancements in quantum computing, the world of cybersecurity is on the brink of a major transformation. While quantum computing promises breakthroughs in various fields, it also poses a significant threat to traditional encryption methods. Many of the cryptographic systems that secure our digital world today, such as RSA and ECC, could become obsolete in the face of quantum-powered attacks. This raises an urgent need for quantum-resistant cryptography, a new class of cryptographic algorithms designed to withstand attacks from quantum computers. What are Quantum Computers Quantum computers are a revolutionary leap beyond classical computing, leveraging the strange and counterintuitive principles of quantum mechanics to process information in ways that are fundamentally different from traditional computers. At the core of a quantum computer are qubits (quantum bits), which, unlike classical bits that can only be 0 or 1, can exist in a superposition of both states simultaneously. This enables quantum computers to perform vast numbers of calculations in parallel, drastically increasing their computational power for certain types of problems. Another key principle is entanglement, where qubits become intrinsically linked, allowing changes to one qubit to instantaneously affect another, no matter the distance between them. This interconnectedness enables faster and more complex computations than classical systems. Additionally, quantum computers leverage quantum interference, manipulating probabilities to guide calculations toward the correct solution. While mainstream applications are still years away, quantum computing has the potential to revolutionize fields from artificial intelligence to materials science, unlocking new levels of computational power never before possible. The Threat of Quantum Computing to Encryption At the core of modern cryptography are mathematical problems that are computationally difficult for classical computers to solve. For instance: RSA (Rivest-Shamir-Adleman) relies on the difficulty of factoring large numbers. Elliptic Curve Cryptography (ECC) is based on the discrete logarithm problem. Diffie-Hellman Key Exchange also depends on the discrete logarithm problem. These cryptographic methods are currently secure because classical computers would take an impractically long time to break them. However, quantum computers leverage principles like superposition and entanglement, allowing them to perform complex calculations exponentially faster than classical machines. One of the biggest threats is Shor’s Algorithm, which, once implemented on a sufficiently powerful quantum computer, could efficiently break RSA and ECC encryption. This means that secure communications, digital signatures, and even blockchain-based systems could be compromised. The "harvest now, decrypt later" strategy is a significant cybersecurity concern, especially in the context of post-quantum cryptography. In this approach, adversaries intercept and store encrypted data today, even if they cannot decrypt it with current technology. The assumption is that once powerful quantum computers become available, these adversaries will be able to break traditional encryption schemes and access the stored data retroactively. What is Quantum-Resistant Cryptography? Quantum-resistant cryptography, also known as post-quantum cryptography (PQC), refers to encryption algorithms that remain secure even in the presence of large-scale quantum computers. These algorithms rely on mathematical problems that are believed to be hard for both classical and quantum computers to solve. Types of Post-Quantum Cryptographic Approaches Lattice-Based Cryptography Based on complex problems related to high-dimensional lattices. One of the most promising areas for quantum-resistant encryption. Examples: Kyber (key encapsulation), Dilithium (digital signatures), BIKE and SIDH (alternative approaches in research). Hash-Based Cryptography Uses cryptographic hash functions to secure data. Proven security but with limitations, mainly in key sizes and signature verification times. Example: SPHINCS+ (a stateless hash-based signature scheme). Code-Based Cryptography Relies on the hardness of decoding error-correcting codes. Example: Classic McEliece, which has been studied for decades and remains unbroken. Multivariate Polynomial Cryptography Uses equations with multiple variables to create cryptographic security. Example: Rainbow (digital signatures). Isogeny-Based Cryptography Based on the complexity of finding isogenies (mathematical maps) between elliptic curves. Example: SIKE (Supersingular Isogeny Key Encapsulation), although recently weakened by cryptanalysis. What is TLS 1.3? TLS 1.3 is the latest iteration of the TLS protocol, designed to provide faster and more secure internet connections. Compared to its predecessor, TLS 1.2, it offers: Reduced Latency TLS 1.3 simplifies the handshake process, reducing the time needed to establish a secure connection. Enhanced Security Older, vulnerable cryptographic algorithms have been removed, making TLS 1.3 resistant to various attacks. Forward Secrecy Ensures that past communications remain secure even if current encryption keys are compromised. How TLS 1.3 Integrates PQC TLS 1.3 is being adapted to include PQC through hybrid key exchange mechanisms. These involve combining traditional cryptographic algorithms with post-quantum counterparts to ensure security against both classical and quantum attacks. Major tech companies and organizations, including Cloudflare, are already testing and deploying PQC in real-world applications. Adoption and Future Outlook The adoption of PQC in TLS 1.3 is steadily increasing, with companies like Cloudflare reporting growing usage in their networks. Early integration allows organizations to future-proof their security before quantum computers become a practical threat. How Organizations Can Prepare for the Quantum Future Stay Informed on Post-Quantum Cryptography Standards NIST has been leading the effort to standardize post-quantum cryptographic algorithms. Organizations should monitor NIST's progress and start evaluating the proposed standards. Identify Cryptographic Dependencies Organizations should conduct a cryptographic inventory to identify where they are using RSA, ECC, and other vulnerable encryption methods. This includes: SSL/TLS certificates VPNs and secure communications Data encryption at rest and in transit Blockchain and digital signatures Begin Hybrid Cryptography Implementations Some security experts recommend a hybrid approach, where systems use both classical and post-quantum cryptography together. This allows for a smooth transition without immediate risks. Upgrade Hardware and Software for Post-Quantum Readiness Quantum-resistant algorithms may require more computational resources. Organizations should assess whether their hardware and software can support these new cryptographic methods. Hardware providers are preparing for the post-quantum era by transitioning their cryptographic signing processes—including firmware, drivers, and software—to quantum-resistant algorithms. Their plans vary, but they generally fall into three categories: Migration to Post-Quantum Cryptographic (PQC) Signing Hardware vendors are working to replace existing digital signature algorithms (e.g., RSA, ECC) with quantum-resistant alternatives, such as those selected by NIST (e.g., CRYSTALS-Dilithium for digital signatures). This process ensures that firmware, drivers, and software remain secure even in the face of future quantum threats. Key Actions: Updating certificate authorities (CAs) to support PQC algorithms. Developing hybrid cryptographic signatures that combine classical and PQC schemes for backward compatibility. Issuing PQC-signed firmware and driver updates for existing hardware. Patching and Retrofitting Existing Hardware For current hardware, vendors are exploring software and firmware updates that integrate PQC-based signing. However, not all legacy devices can be easily updated due to hardware constraints. Key Actions: Issuing firmware updates with PQC signatures where feasible. Providing transition guidance to enterprises on handling mixed cryptographic environments. Collaborating with operating system vendors to ensure PQC-validated driver signing mechanisms. Development of New Hardware with Built-in PQC Support Some vendors are designing next-generation hardware with PQC capabilities embedded at the hardware level. This includes cryptographic modules, TPMs (Trusted Platform Modules), and secure boot mechanisms that natively support PQC algorithms. Key Actions: Designing processors, security chips, and embedded devices with PQC accelerators. Implementing secure boot and attestation processes using PQC algorithms. Ensuring compliance with NIST’s post-quantum cryptography standards. Overall, the transition to PQC signing will involve a mix of software updates, firmware patches, and new hardware development to ensure long-term security against quantum threats. NIST Road Map 2027 and 2030 The National Institute of Standards and Technology (NIST) has laid out a roadmap for the transition to post-quantum cryptography (PQC), recognizing the potential threat posed by quantum computers to classical cryptographic algorithms. The two key milestones you mentioned, 2027 for hardware support and 2030 for mandatory activation, align with the agency’s phased approach to adopting quantum-resistant security. 2027: PQC-Capable Hardware Purchases Mandated (But Not Yet Activated) NIST’s guidance suggests that starting in 2027, all newly procured hardware should include built-in support for PQC, though the capability should not yet be enabled. This approach serves several key purposes: Future-Proofing Infrastructure By requiring hardware to be PQC-capable well in advance of the transition deadline, organizations can ensure that they won’t need to undertake costly and disruptive hardware replacements later. This also allows vendors to gradually integrate PQC into their product lines without forcing immediate adoption. Testing & Compatibility Assurance Having PQC built into the hardware, even if not enabled, allows for extensive real-world testing and validation within existing IT ecosystems. Organizations can assess interoperability with legacy cryptographic algorithms and transition strategies, ensuring smooth deployment when activation becomes mandatory. Security Flexibility There may still be ongoing refinements to PQC standards and implementations between 2027 and 2030. Keeping PQC disabled initially allows organizations to continue using classical cryptographic methods (e.g., RSA, ECC) while planning for a secure migration. 2030: PQC Must Be Activated on All Compliant Hardware By 2030, NIST mandates that PQC must be fully enabled on hardware that was purchased with built-in support. This requirement ensures that all critical systems transition to quantum-safe cryptographic algorithms within a defined timeframe. The rationale behind this activation deadline includes: Mitigating the Quantum Threat As quantum computing advances, the risk of classical cryptographic algorithms (such as RSA and ECC) becoming obsolete increases. Enforcing PQC activation by 2030 ensures that organizations are not left vulnerable to quantum-based attacks. Ensuring a Coordinated Transition By setting a firm deadline, NIST aligns government and industry efforts in adopting standardized PQC protocols. This prevents a fragmented, uncoordinated rollout where some systems remain vulnerable while others have transitioned. Compliance with Federal and Industry Standards Many regulatory frameworks (such as FIPS and CISA cybersecurity directives) will likely incorporate PQC requirements. Enabling PQC by 2030 ensures compliance with these emerging security standards. Avoiding “Harvest Now, Decrypt Later” Attacks Adversaries may already be collecting encrypted data, intending to decrypt it once they obtain a quantum computer capable of breaking classical cryptography. Enabling PQC ensures that sensitive information remains protected against both current and future decryption threats. 34% of Cloudflare HTTPS Requests are PQC According to recent Cloudflare data, roughly 34% of all TLS 1.3 connections established with Cloudflare currently utilize PQC, this is up from 2.83% in the last 12 months (Mar 2024). You can find the latest statistics on Cloudflare Radar Microsoft's PQC Efforts in Windows Server 2022/2025 To support organizations in their PQC transition, Microsoft has integrated post-quantum cryptographic capabilities into its Windows Server environment. The Microsoft PQC API is a key component, enabling developers and IT administrators to: Test and implement quantum-resistant cryptographic algorithms. Ensure compatibility with emerging NIST PQC standards. Gradually transition critical infrastructure to PQC without breaking existing systems. Key Features of the Microsoft PQC API Support for NIST PQC Candidates The API provides access to quantum-resistant algorithms selected by NIST, such as Kyber (for key exchange) and Dilithium (for digital signatures). These algorithms are expected to replace vulnerable public-key encryption methods. Backward Compatibility Windows Server 2022/2025 allows hybrid cryptographic implementations, meaning organizations can use both classical and quantum-resistant algorithms during the transition period. Integration with Windows Cryptographic APIs The PQC API is integrated with existing Windows cryptographic frameworks, including CNG (Cryptography API: Next Generation) and SCHANNEL, enabling easy adoption without major application rewrites. Secure Key Exchange and Authentication The API supports PQC-enabled TLS (Transport Layer Security), allowing secure communication channels that are resistant to quantum threats. How to Get Started with Microsoft's PQC API If you're running Windows Server 2022 or planning to migrate to Windows Server 2025, you can start preparing for post-quantum security with the following steps: Enable PQC Features Ensure your Windows Server instance is updated to the latest version that includes PQC API support. Test PQC Algorithms Use Microsoft’s API to experiment with post-quantum cryptographic primitives in a controlled environment before full-scale deployment. Implement Hybrid Cryptography Transition gradually by using hybrid cryptographic approaches that combine classical and post-quantum algorithms to maintain compatibility while enhancing security. Monitor NIST and Microsoft Updates Stay informed about the latest developments in PQC standards and Microsoft’s implementation roadmap to ensure compliance with future security policies. Conclusion The quantum era is approaching, and while large-scale quantum computers capable of breaking RSA and ECC do not yet exist, organizations must start preparing. The transition to post-quantum cryptography is a complex but necessary shift to protect sensitive data from future threats. By staying informed, assessing cryptographic dependencies, and adopting quantum-resistant strategies, organizations can ensure they remain secure in a post-quantum world.











