Evotec https://evotec.pl/ Services for small, medium and large business Mon, 16 Jun 2025 06:20:58 +0000 pl-PL hourly 1 https://wordpress.org/?v=6.9.4 https://evotec.xyz/wp-content/uploads/2015/05/EvotecFavicon.ico Evotec https://evotec.pl/ 32 32 Supercharging Your Network Diagnostics with Globalping for NET https://evotec.pl/supercharging-your-network-diagnostics-with-globalping-for-net/ Sun, 15 Jun 2025 19:52:52 +0000 https://evotec.xyz/?p=19449 Ever wondered how to run network diagnostics like Ping, Traceroute, or DNS queries from probes scattered across the globe? Enter Globalping.NET, a powerful library that makes it effortless to interact with the Globalping API using C#. Whether you're debugging latency issues or exploring packet routes, this library has you covered.

The post Supercharging Your Network Diagnostics with Globalping for NET appeared first on Evotec.

]]>

Ever wondered how to run network diagnostics like Ping, Traceroute, or DNS queries from probes scattered across the globe? Enter Globalping.NET, a powerful library that makes it effortless to interact with the Globalping API using C#. Whether you're debugging latency issues or exploring packet routes, this library has you covered.

In this blog, we'll dive into the features of Globalping.NET, explore real-world examples, and show you how to get started with this amazing tool. If you're a PowerShell enthusiast, check out our companion blog on Globalping PowerShell Module to see how these tools complement each other.

Free API with Generous Limits

Globalping is a free API that requires no registration to get started. Here are the limits:

  • Unregistered Users:
    • 50 probes per measurement
    • 250 free tests per hour
  • Registered Users:
    • 500 probes per measurement
    • 500 free tests per hour

Higher limits are available for members, making it ideal for both casual and professional use.

Available Methods

Globalping.NET provides a rich set of methods to interact with the Globalping API:

  • MeasurementRequestBuilder: Fluent API for building measurement requests.
  • ProbeService: Submit measurements and retrieve probe information.
  • MeasurementClient: Poll for results and handle caching.
  • GetPingTimings: Extract timing results from Ping measurements.
  • GetTracerouteHops: Retrieve hop details from Traceroute measurements.
  • GetDnsRecords: Parse DNS results into structured records.
  • HttpOptions: Customize HTTP requests with headers, methods, and query strings.

With these methods, you can create, execute, and analyze network diagnostics with ease, all while leveraging advanced features like Brotli compression and ETag caching.

Why Globalping.NET?

Globalping.NET is your gateway to running network diagnostics from hundreds of probes worldwide. With support for Ping, Traceroute, MTR, DNS, and HTTP measurements, it simplifies complex tasks into a few lines of code. Here's why you should consider it:

  • Ease of Use: Fluent APIs for building requests.
  • Flexibility: Control probe locations, limits, and measurement options.
  • Rich Features: Built-in caching, Brotli compression, and ETag support.

Getting Started

Installation

Add the package from NuGet:

> dotnet add package Globalping

Alternatively, reference the project directly if you're working within the repository.

Real-World Examples

Running Measurements Without API Key

You can start using Globalping without an API key. Here's how to run a Ping from Germany:

using var httpClient = new HttpClient();
var builder = new MeasurementRequestBuilder()
    .WithType(MeasurementType.Ping)
    .WithTarget("cdn.jsdelivr.net")
    .AddCountry("DE")
    .Build();

var probeService = new ProbeService(httpClient);
var measurement = await probeService.CreateMeasurementAsync(builder);
Console.WriteLine($"Measurement ID: {measurement.Id}");

Running Measurements With API Key

For higher limits and more probes, use the apiKey parameter:

using var httpClient = new HttpClient();
var builder = new MeasurementRequestBuilder()
    .WithType(MeasurementType.Ping)
    .WithTarget("cdn.jsdelivr.net")
    .AddCountry("DE")
    .Build();

var probeService = new ProbeService(httpClient, "your-api-key");
var measurement = await probeService.CreateMeasurementAsync(builder);
Console.WriteLine($"Measurement ID: {measurement.Id}");

Advanced HTTP Options

For HTTP measurements, you can specify methods, paths, and query strings. Here's an example:

var builder = new MeasurementRequestBuilder()
    .WithType(MeasurementType.Http)
    .WithTarget("https://example.com/api/data?x=1")
    .WithMeasurementOptions(new HttpOptions
    {
        Request = new HttpRequestOptions
        {
            Method = HttpRequestMethod.GET,
            Path = "/api/data",
            Query = "x=1"
        },
        Protocol = HttpProtocol.HTTPS,
        IpVersion = IpVersion.Six
    });

var request = builder.Build();
using var httpClient = new HttpClient();
var probeService = new ProbeService(httpClient);
var measurement = await probeService.CreateMeasurementAsync(request);

Console.WriteLine($"HTTP Measurement ID: {measurement.Id}");

Monitoring Limits

Keep an eye on your API usage with the GetLimitsAsync method:

using var httpClient = new HttpClient();
var probeService = new ProbeService(httpClient, "your-api-key");
var limits = await probeService.GetLimitsAsync();

Console.WriteLine($"Credits Remaining: {limits.CreditsRemaining}");

Cross-Platform Diagnostics

If you're working in both C# and PowerShell, the Globalping ecosystem has you covered. Use Globalping PowerShell Module for quick scripting and automation, and leverage Globalping.NET for robust application development. Together, they form a powerful toolkit for network diagnostics.

Repository

Explore the source code and contribute to the project on GitHub: Globalping Repository

Conclusion

Globalping.NET is a game-changer for network diagnostics. With its intuitive API and powerful features, you can run measurements from diverse locations, control probe behavior, and handle caching with ease. Explore the example project in this repository for more scenarios and advanced usages.

Ready to supercharge your diagnostics? Install Globalping.NET today and start exploring the world of network measurements!

The post Supercharging Your Network Diagnostics with Globalping for NET appeared first on Evotec.

]]>
Automating Network Diagnostics with Globalping PowerShell Module https://evotec.pl/automating-network-diagnostics-with-globalping-powershell-module/ Sun, 15 Jun 2025 19:52:50 +0000 https://evotec.xyz/?p=19451 Are you tired of manually running network diagnostics like Ping, Traceroute, or DNS queries? The Globalping PowerShell Module is here to save the day! With its easy-to-use cmdlets, you can automate measurements from probes distributed across the globe.

The post Automating Network Diagnostics with Globalping PowerShell Module appeared first on Evotec.

]]>

Are you tired of manually running network diagnostics like Ping, Traceroute, or DNS queries? The Globalping PowerShell Module is here to save the day! With its easy-to-use cmdlets, you can automate measurements from probes distributed across the globe.

In this blog, we'll explore the features of the Globalping PowerShell Module, showcase real-world examples, and help you get started with scripting your network tests. If you're a developer working in C#, check out our companion blog on Globalping.NET to see how these tools complement each other.

Free API with Generous Limits

Globalping is a free API that requires no registration to get started. Here are the limits:

  • Unregistered Users:
    • 50 probes per measurement
    • 250 free tests per hour
  • Registered Users:
    • 500 probes per measurement
    • 500 free tests per hour

Higher limits are available for members, making it ideal for both casual and professional use.

Available Cmdlets

The Globalping PowerShell Module offers a rich set of cmdlets to cover all your network diagnostic needs:

  • Start-GlobalpingPing: Run ICMP ping from remote probes and return timing results or classic text.
  • Start-GlobalpingTraceroute: Display each hop packets take to the target host.
  • Start-GlobalpingMtr: Perform an MTR trace that collects per-hop statistics.
  • Start-GlobalpingDns: Resolve domain names using remote resolvers.
  • Start-GlobalpingHttp: Send HTTP requests or header checks from probes.
  • Get-GlobalpingProbe: List available probes with location information.
  • Get-GlobalpingLimit: Inspect current API usage limits and remaining credits.

With these cmdlets, you can script everything from simple pings to advanced HTTP diagnostics, all while monitoring your API usage and probe availability.

Why Globalping PowerShell?

Globalping PowerShell simplifies network diagnostics by bundling the Globalping API into cmdlets. Here's why it's awesome:

  • Cmdlet Simplicity: No need to deal with raw HTTP requests.
  • Location Control: Run diagnostics from specific countries, cities, or even cloud providers.
  • Rich Output: Retrieve detailed results in table or list formats.

Getting Started

Installation

Install the module from the PowerShell Gallery:

Install-Module Globalping -Scope CurrentUser
Import-Module Globalping

Working directly with the repository? Clone and build it:

cd Globalping
 dotnet build
Import-Module ./Module/Globalping.psd1 -Force

Real-World Examples

Running Measurements Without API Key

You can start using Globalping without an API key. Here's how to run a Ping from Germany:

Start-GlobalpingPing -Target "example.com" -SimpleLocations "DE"

Want to run from multiple locations? Just add more countries:

Start-GlobalpingPing -Target "example.com" -SimpleLocations "DE", "US", "GB"

Running Measurements With API Key

For higher limits and more probes, use the -ApiKey parameter:

Start-GlobalpingPing -Target "example.com" -SimpleLocations "DE" -ApiKey "your-api-key"

DNS Queries

Resolve domain names with ease:

Start-GlobalpingDns -Target "evotec.xyz" -Verbose | Format-Table *

Retrieve raw DNS results:

$OutputDns = Start-GlobalpingDns -Target "evotec.xyz" -Verbose -Raw
$OutputDns.Results[0].ToDnsRecords() | Format-Table *

HTTP Measurements

Send HTTP requests or check headers from probes:

$Output = Start-GlobalpingHttp -Target "evotec.xyz" -Verbose -SimpleLocations "Krakow+PL"
$Output.Headers | Format-Table

Retrieve raw HTTP results:

$OutputHttp = Start-GlobalpingHttp -Target "evotec.xyz" -Verbose -Raw
$OutputHttp.Results[0].Data.Headers | Format-Table

MTR and Traceroute

Perform MTR traces with detailed hop statistics:

$OutputMtr = Start-GlobalpingMtr -Target "evotec.xyz" -Verbose -Raw -SimpleLocations "Krakow+PL", "Berlin+DE"
$OutputMtr.Results[0].ToMtrHops() | Format-Table *

Run a Traceroute:

Start-GlobalpingTraceroute -Target "evotec.xyz" -Verbose | Format-Table *

Probes and Limits

List available probes:

Get-GlobalpingProbe | Select-Object -First 5 | Format-Table *

Monitor your API usage:

Get-GlobalpingLimit -ApiKey "your-api-key" | Format-Table

Retrieve raw limit information:

Get-GlobalpingLimit -ApiKey "your-api-key" -Raw | Format-List

Cross-Platform Diagnostics

If you're working in both PowerShell and C#, the Globalping ecosystem has you covered. Use the PowerShell module for quick scripting and automation, and leverage Globalping.NET for robust application development. Together, they form a powerful toolkit for network diagnostics.

Repository

Explore the source code and contribute to the project on GitHub: Globalping Repository

Conclusion

The Globalping PowerShell Module is a must-have for automating network diagnostics. With its intuitive cmdlets and powerful features, you can run measurements from diverse locations, fetch detailed results, and monitor your API limits effortlessly.

Ready to automate your network tests? Install the Globalping PowerShell Module today and start scripting your diagnostics!

The post Automating Network Diagnostics with Globalping PowerShell Module appeared first on Evotec.

]]>
Enhanced Dashboards with PSWriteHTML – Introducing InfoCards and Density Options https://evotec.pl/enhanced-dashboards-with-pswritehtml-introducing-infocards-and-density-options/ Wed, 04 Jun 2025 13:54:28 +0000 https://evotec.xyz/?p=18902 Discover new features in the PSWriteHTML PowerShell module – including New-HTMLInfoCard, improved layout controls with the -Density parameter, and customizable shadows for clean, modern dashboards and reports.

The post Enhanced Dashboards with PSWriteHTML – Introducing InfoCards and Density Options appeared first on Evotec.

]]>

Creating intuitive, visually appealing HTML reports or dashboards can significantly enhance readability and usability. Today, I'm introducing several powerful additions to the PSWriteHTML PowerShell module:

  • New-HTMLInfoCard
  • Improved New-HTMLSection, New-HTMLPanel, and New-HTMLContainer
  • A versatile -Density parameter for flexible layout management
  • Rich shadow customization

Let's dive into each feature, with practical examples and screenshots, helping you quickly integrate these into your workflows.

💡 New-HTMLInfoCard – Clear and Informative Cards

New-HTMLInfoCard is perfect for displaying key metrics or summaries clearly and attractively. It supports:

  • Icons (emoji, predefined sets, FontAwesome 500+ icons)
  • Customizable colors
  • Rich shadow effects
  • Multiple styles (Standard, Compact, Classic, NoIcon)

Here's an example showing how New-HTMLInfoCard is used in different ways to show data

This nice looking infocard examples are done using single line of code New-HTMLInfoCard

New-HTML {
    New-HTMLPanelOption -BorderRadius 0px
    New-HTMLSectionOption -BorderRadius 0px -HeaderBackGroundColor AirForceBlue

    # Standard Style Cards
    New-HTMLSection -HeaderText "Standard Style" {
        New-HTMLSection -Invisible {
            New-HTMLInfoCard -Title "Total Users" -Number 47 -Subtitle "21.28% of users" -Icon "👥" -IconColor "#0078d4" -BorderRadius 0px
            New-HTMLInfoCard -Title "MFA Capable Users" -Number 10 -Subtitle "21.28% of users" -Icon "🔒" -IconColor "#21c87a" -BorderRadius 0px
            New-HTMLInfoCard -Title "Strong Auth Methods" -Number 8 -Subtitle "17.02% of users" -Icon "💪" -IconColor "#ffb300" -BorderRadius 0px
            New-HTMLInfoCard -Title "Passwordless Capable" -Number 2 -Subtitle "4.26% of users" -Icon "🔑" -IconColor "#d13438" -BorderRadius 0px
        }
    }

    New-HTMLSection -Density Compact -Invisible {
        New-HTMLInfoCard -Title "Total Users" -Number 47 -Subtitle "21.28% of users" -Icon "👥" -IconColor "#0078d4" -BorderRadius 0px
        New-HTMLInfoCard -Title "MFA Capable Users" -Number 10 -Subtitle "21.28% of users" -Icon "🔒" -IconColor "#21c87a" -BorderRadius 0px
        New-HTMLInfoCard -Title "Strong Auth Methods" -Number 8 -Subtitle "17.02% of users" -Icon "💪" -IconColor "#ffb300" -BorderRadius 0px
        New-HTMLInfoCard -Title "Passwordless Capable" -Number 2 -Subtitle "4.26% of users" -Icon "🔑" -IconColor "#d13438" -BorderRadius 0px
    }

    New-HTMLSection -Invisible {
        New-HTMLInfoCard -Title "Total Users" -Number 47 -Subtitle "21.28% of users" -Icon "👥" -IconColor "#0078d4" -BorderRadius 0px
        New-HTMLInfoCard -Title "MFA Capable Users" -Number 10 -Subtitle "21.28% of users" -Icon "🔒" -IconColor "#21c87a" -BorderRadius 0px
        New-HTMLInfoCard -Title "Strong Auth Methods" -Number 8 -Subtitle "17.02% of users" -Icon "💪" -IconColor "#ffb300" -BorderRadius 0px
        New-HTMLInfoCard -Title "Passwordless Capable" -Number 2 -Subtitle "4.26% of users" -Icon "🔑" -IconColor "#d13438" -BorderRadius 0px
    }

    # Compact Style Cards
    New-HTMLSection -HeaderText "Compact Style" {
        New-HTMLSection -Invisible {
            New-HTMLInfoCard -Title "Server CPU" -Number "85%" -Subtitle "Usage" -Icon "Server" -IconColor "#0078d4" -Style "Compact"
            New-HTMLInfoCard -Title "Memory" -Number "12GB" -Subtitle "Available" -Icon "Database" -IconColor "#21c87a" -Style "Compact"
            New-HTMLInfoCard -Title "Storage" -Number "500GB" -Subtitle "Free space" -Icon "Cloud" -IconColor "#ffb300" -Style "Compact"
            New-HTMLInfoCard -Title "Network" -Number "1.2Gbps" -Subtitle "Throughput" -Icon "Network" -IconColor "#d13438" -Style "Compact"
        }
    }

    # Fixed Layout Style (Multiline Safe)
    New-HTMLSection -HeaderText "Fixed Layout (Multiline Safe)" {
        New-HTMLSection -Invisible {
            New-HTMLInfoCard -Title "Total Users with Very Long Title That Wraps" -Number 47 -Subtitle "21.28% of users" -Icon "Users" -IconColor "#0078d4" -Style "Fixed"
            New-HTMLInfoCard -Title "MFA Capable Users" -Number 10 -Subtitle "21.28% of users" -Icon "Lock" -IconColor "#21c87a" -Style "Fixed"
            New-HTMLInfoCard -Title "Strong Authentication Methods Available" -Number 8 -Subtitle "17.02% of users" -Icon "💪" -IconColor "#ffb300" -Style "Fixed"
            New-HTMLInfoCard -Title "Passwordless Capable" -Number 2 -Subtitle "4.26% of users" -Icon "Key" -IconColor "#d13438" -Style "Fixed"
        }
    }

    # Classic Style Cards
    New-HTMLSection -HeaderText "Classic Style" {
        New-HTMLSection -Invisible {
            New-HTMLInfoCard -Title "132 Sales" -Subtitle "12 waiting payments" -Icon Money -IconColor "#0078d4" -Style "Classic"
            New-HTMLInfoCard -Title "98 Completed" -Subtitle "Tasks finished today" -Icon "Check" -IconColor "#21c87a" -Style "Classic"
            New-HTMLInfoCard -Title "5 Pending" -Subtitle "Requires attention" -Icon "Warning" -IconColor "#ffb300" -Style "Classic"
        }
    }

    # No Icon Style Cards
    New-HTMLSection -HeaderText "No Icons Style" {
        New-HTMLSection -Invisible {
            New-HTMLInfoCard -Title "Revenue" -Number "`$45,320" -Subtitle "This month" -Style "NoIcon" -NumberColor "#21c87a" -Alignment "Left"
            New-HTMLInfoCard -Title "Orders" -Number "1,234" -Subtitle "Processed today" -Style "NoIcon" -NumberColor "#0078d4" -Alignment "Center"
            New-HTMLInfoCard -Title "Customers" -Number "5,678" -Subtitle "Active users" -Style "NoIcon" -NumberColor "#ffb300" -Alignment "Right"
        }
    }

    # Using FontAwesome Icons
    New-HTMLSection -HeaderText "FontAwesome Icons" {
        New-HTMLSection -Invisible {
            New-HTMLInfoCard -Title "Analytics Report" -Number 156 -Subtitle "Reports generated" -IconSolid "chart-bar" -IconColor "#6f42c1"
            New-HTMLInfoCard -Title "Security Status" -Number "99.9%" -Subtitle "Uptime" -IconSolid "shield-alt" -IconColor "#28a745"
            New-HTMLInfoCard -Title "API Calls" -Number "2.3M" -Subtitle "This week" -IconSolid "plug" -IconColor "#17a2b8"
            New-HTMLInfoCard -Title "Sync Status" -Number "Active" -Subtitle "Last sync: 2 min ago" -IconSolid "sync-alt" -IconColor "#ffc107"
        }
    }

    # Using Icon Dictionary
    New-HTMLSection -HeaderText "Using Icon Dictionary" {
        New-HTMLSection -Invisible {
            New-HTMLInfoCard -Title "Analytics Report" -Number 156 -Subtitle "Reports generated" -Icon "Chart" -IconColor "#6f42c1"
            New-HTMLInfoCard -Title "Security Status" -Number "99.9%" -Subtitle "Uptime" -Icon "Shield" -IconColor "#28a745"
            New-HTMLInfoCard -Title "API Calls" -Number "2.3M" -Subtitle "This week" -Icon "Api" -IconColor "#17a2b8"
            New-HTMLInfoCard -Title "Sync Status" -Number "Active" -Subtitle "Last sync: 2 min ago" -Icon "Sync" -IconColor "#ffc107"
        }
    }

    # Alignment Examples
    New-HTMLSection -HeaderText "Alignment Examples" {
        New-HTMLSection -Invisible {
            New-HTMLInfoCard -Title "Left Aligned" -Number "Default" -Subtitle "Standard alignment" -Icon "📊" -IconColor "#6f42c1" -Alignment "Left"
            New-HTMLInfoCard -Title "Center Aligned" -Number "Centered" -Subtitle "Perfect for dashboards" -Icon "⚖" -IconColor "#28a745" -Alignment "Center"
            New-HTMLInfoCard -Title "Right Aligned" -Number "Right Side" -Subtitle "Alternative layout" -Icon "➡" -IconColor "#17a2b8" -Alignment "Right"
        }
    }

    # Color Customization Examples
    New-HTMLSection -HeaderText "Color Customization" {
        New-HTMLSection -Invisible {
            New-HTMLInfoCard -Title "Custom Colors" -Number "Beautiful" -Subtitle "Title, Number & Subtitle colors" -Icon "🎨" -IconColor "#e74c3c" -TitleColor "#2c3e50" -NumberColor "#e74c3c" -SubtitleColor "#7f8c8d" -BorderRadius 0px
            New-HTMLInfoCard -Title "Shadow Effects" -Number "Amazing" -Subtitle "Custom shadow colors" -Icon "✨" -IconColor "#f39c12" -ShadowColor "rgba(243, 156, 18, 0.3)" -ShadowDirection "All" -BorderRadius 0px
            New-HTMLInfoCard -Title "Directional Shadow" -Number "Right Side" -Subtitle "Shadow to the right" -Icon "➡" -IconColor "#9b59b6" -ShadowDirection "Right" -ShadowColor "rgba(155, 89, 182, 0.4)" -BorderRadius 0px
        }
    }
} -FilePath "$PSScriptRoot\Example-InfoCard.html" -Online -Show

💡 Introducing Density Parameter

For this feature to be even better I've introduced the new -Density parameter lets you precisely control the layout density of your dashboards. Available options:

  • Spacious: Larger cards, plenty of whitespace, ideal for clarity
  • Comfortable: Balanced spacing, great for general use
  • Compact: Optimized space for detailed views
  • Dense: Tightly packed layout for maximum data display
  • VeryDense: Minimal space, ideal for extensive metrics

Example:

New-HTML {
    New-HTMLSection -HeaderText "🎯 New Density Parameter - Intuitive Layout Control!" -Wrap wrap {
        New-HTMLSection -HeaderText "📏 Spacious Density" -Density Spacious {
            New-HTMLInfoCard -Title "Identity Protection" -Number "Active" -Subtitle "Large cards with plenty of space" -Icon "🛡" -IconColor '#0078d4'
            New-HTMLInfoCard -Title "Access Reviews" -Number "Enabled" -Subtitle "Roomy layout for better readability" -Icon "👥" -IconColor '#0078d4'
            New-HTMLInfoCard -Title "Authentication" -Number "Secure" -Subtitle "Spacious density for important content" -Icon "🔑" -IconColor '#0078d4'
        }

        New-HTMLSection -HeaderText "🏠 Comfortable Density" -Density Comfortable {
            New-HTMLInfoCard -Title "Microsoft Entra" -Number "100%" -Subtitle "Balanced card size" -Icon "🔷" -IconColor '#198754'
            New-HTMLInfoCard -Title "Tenant Settings" -Number "OK" -Subtitle "Comfortable spacing" -Icon "⚙" -IconColor '#198754'
            New-HTMLInfoCard -Title "Permissions" -Number "Managed" -Subtitle "Good balance of space" -Icon "📁" -IconColor '#198754'
            New-HTMLInfoCard -Title "Security" -Number "High" -Subtitle "Neither too big nor small" -Icon "🔒" -IconColor '#198754'
        }

        New-HTMLSection -HeaderText "📦 Compact Density" -Density Compact {
            New-HTMLInfoCard -Title "Users" -Number "1,250" -Subtitle "Efficient layout" -Icon "👤" -IconColor '#6f42c1' -Style "Compact"
            New-HTMLInfoCard -Title "Groups" -Number "45" -Subtitle "Space-efficient" -Icon "👥" -IconColor '#6f42c1' -Style "Compact"
            New-HTMLInfoCard -Title "Apps" -Number "123" -Subtitle "Compact design" -Icon "📱" -IconColor '#6f42c1' -Style "Compact"
            New-HTMLInfoCard -Title "Devices" -Number "890" -Subtitle "Good density" -Icon "💻" -IconColor '#6f42c1' -Style "Compact"
            New-HTMLInfoCard -Title "Policies" -Number "12" -Subtitle "Organized layout" -Icon "📋" -IconColor '#6f42c1' -Style "Compact"
        }

        New-HTMLSection -HeaderText "🏗 Dense Density" -Density Dense {
            New-HTMLInfoCard -Title "CPU" -Number "45%" -Subtitle "High density" -Icon "⚡" -IconColor '#ffc107' -Style "Compact"
            New-HTMLInfoCard -Title "Memory" -Number "67%" -Subtitle "More per row" -Icon "💾" -IconColor '#ffc107' -Style "Compact"
            New-HTMLInfoCard -Title "Disk" -Number "23%" -Subtitle "Dense layout" -Icon "💿" -IconColor '#ffc107' -Style "Compact"
            New-HTMLInfoCard -Title "Network" -Number "12%" -Subtitle "Packed tight" -Icon "🌐" -IconColor '#ffc107' -Style "Compact"
            New-HTMLInfoCard -Title "Uptime" -Number "99.9%" -Subtitle "Efficient use" -Icon "⏰" -IconColor '#ffc107' -Style "Compact"
            New-HTMLInfoCard -Title "Load" -Number "Low" -Subtitle "Maximum info" -Icon "📊" -IconColor '#ffc107' -Style "Compact"
        }

        New-HTMLSection -HeaderText "🔬 VeryDense Density" -Density VeryDense {
            New-HTMLInfoCard -Title "A1" -Number "✓" -Subtitle "Tiny cards" -Icon "1⃣" -IconColor '#dc3545' -Style "Compact"
            New-HTMLInfoCard -Title "B2" -Number "✓" -Subtitle "Max density" -Icon "2⃣" -IconColor '#dc3545' -Style "Compact"
            New-HTMLInfoCard -Title "C3" -Number "✓" -Subtitle "Lots of info" -Icon "3⃣" -IconColor '#dc3545' -Style "Compact"
            New-HTMLInfoCard -Title "D4" -Number "✓" -Subtitle "In small space" -Icon "4⃣" -IconColor '#dc3545' -Style "Compact"
            New-HTMLInfoCard -Title "E5" -Number "✓" -Subtitle "Very dense" -Icon "5⃣" -IconColor '#dc3545' -Style "Compact"
            New-HTMLInfoCard -Title "F6" -Number "✓" -Subtitle "Compact view" -Icon "6⃣" -IconColor '#dc3545' -Style "Compact"
            New-HTMLInfoCard -Title "G7" -Number "✓" -Subtitle "Dashboard" -Icon "7⃣" -IconColor '#dc3545' -Style "Compact"
            New-HTMLInfoCard -Title "H8" -Number "✓" -Subtitle "Overview" -Icon "8⃣" -IconColor '#dc3545' -Style "Compact"
        }

        New-HTMLText -Text @"
<h3>📝 How to Use:</h3>
<pre><code>
New-HTMLSection -Density Spacious { }
New-HTMLPanel -Density Comfortable { }
New-HTMLContainer -Density Dense { }</code></pre>

<p><strong>🎯 Perfect!</strong> Now the parameter name matches what it does - control the density/spacing of your layout!</p>
<p><em>Resize your browser window to see responsive behavior in action.</em></p>
"@ -Color Black
    }
} -FilePath "$PSScriptRoot\Example-DensityTest.html" -TitleText "Density Parameter Demo" -Online -Show

Once you resize your browser you will see the cards adapt, maximizing readability across all devices.

Further resizing changes how the cards look like based on the density settings

💡 Advanced Shadow Customization

If you want to make the cards/dashboards your own – Shadows can significantly enhance visual presentation. You can customize shadows with:

  • Intensity presets: None, Subtle, Normal, Bold, ExtraBold
  • Directional shadows: Top, Bottom, Left, Right, All
  • Custom blur and spread
  • Custom color overlays

Example:

New-HTML {
    New-HTMLSectionStyle -BorderRadius 0px -HeaderBackGroundColor 'DarkSlateGray'

    # Shadow Intensity Presets Section
    New-HTMLSection -HeaderText 'Shadow Intensity Presets'  {
        New-HTMLSection -Invisible  {
            # None - No shadow
            New-HTMLInfoCard -Title "No Shadow" -Number "Clean" -Subtitle "Minimal look" -Icon "💫" -ShadowIntensity 'None'

            # Subtle - Very light shadow
            New-HTMLInfoCard -Title "Subtle Shadow" -Number "Light" -Subtitle "Barely visible" -Icon "👻" -ShadowIntensity 'Subtle'

            # Normal - Standard shadow (default)
            New-HTMLInfoCard -Title "Normal Shadow" -Number "Standard" -Subtitle "Default setting" -Icon "📊" -ShadowIntensity 'Normal'

            # Bold - Strong, very visible shadow
            New-HTMLInfoCard -Title "Bold Shadow" -Number "Strong" -Subtitle "Very visible" -Icon "🎯" -ShadowIntensity 'Bold'

            # ExtraBold - Maximum impact shadow
            New-HTMLInfoCard -Title "Extra Bold" -Number "Maximum" -Subtitle "Super visible!" -Icon "💥" -ShadowIntensity 'ExtraBold'
        }
    }

    # Custom Shadow Controls Section
    New-HTMLSection -HeaderText 'Custom Shadow Controls' {
        New-HTMLSection -Invisible {
            # Custom blur radius
            New-HTMLInfoCard -Title "Custom Blur" -Number "Blur 20" -Subtitle "Very soft edges" -Icon "🌊" -ShadowIntensity 'Custom' -ShadowBlur 20 -ShadowColor 'rgba(0, 100, 200, 0.3)'

            # Custom spread
            New-HTMLInfoCard -Title "Custom Spread" -Number "Spread 6" -Subtitle "Extended shadow" -Icon "📡" -ShadowIntensity 'Custom' -ShadowBlur 12 -ShadowSpread 6 -ShadowColor 'rgba(200, 0, 100, 0.25)'

            # Extreme custom settings
            New-HTMLInfoCard -Title "Extreme Custom" -Number "Max Power" -Subtitle "All settings maxed" -Icon "⚡" -ShadowIntensity 'Custom' -ShadowBlur 25 -ShadowSpread 8 -ShadowColor 'rgba(255, 0, 0, 0.4)'
        }
    }

    # Bold Shadows with Different Directions
    New-HTMLSection -HeaderText 'Bold Shadows - All Directions' {
        New-HTMLSection -Invisible {
            New-HTMLInfoCard -Title "Bold Bottom" -Number "⬇" -Subtitle "Downward impact" -Icon "📈" -ShadowIntensity 'Bold' -ShadowDirection 'Bottom'
            New-HTMLInfoCard -Title "Bold Top" -Number "⬆" -Subtitle "Upward lift" -Icon "🚀" -ShadowIntensity 'Bold' -ShadowDirection 'Top'
            New-HTMLInfoCard -Title "Bold Left" -Number "⬅" -Subtitle "Westward depth" -Icon "🌅" -ShadowIntensity 'Bold' -ShadowDirection 'Left'
            New-HTMLInfoCard -Title "Bold Right" -Number "➡" -Subtitle "Eastward depth" -Icon "🌇" -ShadowIntensity 'Bold' -ShadowDirection 'Right'
            New-HTMLInfoCard -Title "Bold All Around" -Number "💡" -Subtitle "360° glow effect" -Icon "🔆" -ShadowIntensity 'Bold' -ShadowDirection 'All'
        }
    }

    # Colored Bold Shadows
    New-HTMLSection -HeaderText 'Colored Bold Shadows' {
        New-HTMLSection -Invisible {
            # Bold red shadow
            New-HTMLInfoCard -Title "Bold Red" -Number "Fire" -Subtitle "Intense red glow" -Icon "🔥" -ShadowIntensity 'Bold' -ShadowColor 'rgba(255, 0, 0, 0.4)'

            # Bold blue shadow
            New-HTMLInfoCard -Title "Bold Blue" -Number "Ocean" -Subtitle "Deep blue depth" -Icon "🌊" -ShadowIntensity 'Bold' -ShadowColor 'rgba(0, 100, 255, 0.4)'

            # Bold green shadow
            New-HTMLInfoCard -Title "Bold Green" -Number "Nature" -Subtitle "Forest green aura" -Icon "🌲" -ShadowIntensity 'Bold' -ShadowColor 'rgba(0, 200, 50, 0.4)'

            # Bold purple shadow
            New-HTMLInfoCard -Title "Bold Purple" -Number "Magic" -Subtitle "Mystical purple" -Icon "🔮" -ShadowIntensity 'Bold' -ShadowColor 'rgba(150, 0, 255, 0.4)'

            # Bold gold shadow
            New-HTMLInfoCard -Title "Bold Gold" -Number "Wealth" -Subtitle "Golden luxury" -Icon "👑" -ShadowIntensity 'Bold' -ShadowColor 'rgba(255, 215, 0, 0.5)'
        }
    }

    # ExtraBold Showcase
    New-HTMLSection -HeaderText 'ExtraBold - Maximum Impact' {
        New-HTMLSection -Invisible {
            New-HTMLInfoCard -Title "MAXIMUM IMPACT" -Number "100%" -Subtitle "Can't miss this shadow!" -Icon "💥" -ShadowIntensity 'ExtraBold' -ShadowColor 'rgba(0, 0, 0, 0.6)'
            New-HTMLInfoCard -Title "SUPER VISIBLE" -Number "BOLD" -Subtitle "Extremely prominent" -Icon "⚡" -ShadowIntensity 'ExtraBold' -ShadowColor 'rgba(255, 50, 50, 0.5)'
            New-HTMLInfoCard -Title "ULTRA DEPTH" -Number "DEEP" -Subtitle "Maximum 3D effect" -Icon "🎯" -ShadowIntensity 'ExtraBold' -ShadowDirection 'All'
        }
    }

} -FilePath "$PSScriptRoot\Example-InfoCard-ShadowShowcase.html" -TitleText "InfoCard Shadow Showcase" -Online -Show

💡 Other features mixed up

Here's another example that shows couple of info cards in action, along with older features such as New-HTMLSectionStyle, New-HTMLImage, and New-HTMLDate.

And the code for that nice example:

New-HTML {
    New-HTMLHeader {
        New-HTMLSection -Invisible {
            New-HTMLPanel -Invisible {
                New-HTMLImage -Source 'https://evotec.pl/wp-content/uploads/2015/05/Logo-evotec-012.png' -UrlLink 'https://evotec.pl/' -AlternativeText 'My other text' -Class 'otehr' -Width '50%'
            }
            New-HTMLPanel -Invisible {
                New-HTMLImage -Source 'https://evotec.pl/wp-content/uploads/2015/05/Logo-evotec-012.png' -UrlLink 'https://evotec.pl/' -AlternativeText 'My other text' -Width '20%'
            } -AlignContentText right
        }
        New-HTMLPanel {
            New-HTMLText -Text "Report generated on ", (New-HTMLDate -InputDate (Get-Date)) -Color None, Blue -FontSize 10, 10
            New-HTMLText -Text "Report generated on ", (New-HTMLDate -InputDate (Get-Date -Year 2022)) -Color None, Blue -FontSize 10, 10
            New-HTMLText -Text "Report generated on ", (New-HTMLDate -InputDate (Get-Date -Year 2022) -DoNotIncludeFromNow) -Color None, Blue -FontSize 10, 10
            New-HTMLText -Text "Report generated on ", (New-HTMLDate -InputDate (Get-Date -Year 2024 -Month 11)) -Color None, Blue -FontSize 10, 10
        } -Invisible -AlignContentText right
    }
    New-HTMLSectionStyle -BorderRadius 0px -HeaderBackGroundColor '#0078d4'

    # Feature highlights section - now with ResponsiveWrap
    New-HTMLSection -Density Dense {
        # Identity Protection
        New-HTMLInfoCard -Title "Identity Protection" -Subtitle "View risky users, risky workload identities, and risky sign-ins in your tenant." -Icon "🛡" -IconColor "#0078d4" -Style "Standard" -ShadowIntensity 'Normal' -BorderRadius 2px -BackgroundColor Azure

        # # Access reviews
        New-HTMLInfoCard -Title "Access reviews" -Subtitle "Make sure only the right people have continued access." -Icon "👥" -IconColor "#0078d4" -Style "Standard" -ShadowIntensity 'Normal' -BorderRadius 2px -BackgroundColor Salmon

        # # Authentication methods
        New-HTMLInfoCard -Title "Authentication methods" -Subtitle "Configure your users in the authentication methods policy to enable passwordless authentication." -Icon "🔑" -IconColor "#0078d4" -Style "Standard" -ShadowIntensity 'Normal' -BorderRadius 2px -ShadowColor Salmon

        # # Microsoft Entra Domain Services
        New-HTMLInfoCard -Title "Microsoft Entra Domain Services" -Subtitle "Lift-and-shift legacy applications running on-premises into Azure." -Icon "🔷" -IconColor "#0078d4" -Style "Standard" -ShadowIntensity 'Normal' -BorderRadius 2px

        # # Tenant restrictions
        New-HTMLInfoCard -Title "Tenant restrictions" -Subtitle "Specify the list of tenants that their users are permitted to access." -Icon "🚫" -IconColor "#dc3545" -Style "Standard" -ShadowIntensity 'Normal' -BorderRadius 2px

        # # Entra Permissions Management
        New-HTMLInfoCard -Title "Entra Permissions Management" -Subtitle "Continuous protection of your critical cloud resources from accidental misuse and malicious exploitation of permissions." -Icon "📁" -IconColor "#198754" -Style "Standard" -ShadowIntensity 'Normal' -BorderRadius 2px

        # # Privileged Identity Management
        New-HTMLInfoCard -Title "Privileged Identity Management" -Subtitle "Manage, control, and monitor access to important resources in your organization." -Icon "💎" -IconColor "#6f42c1" -Style "Standard" -ShadowIntensity 'Normal' -BorderRadius 2px

        # Conditional Access
        New-HTMLInfoCard -Title "Conditional Access" -Subtitle "Control user access based on Conditional Access policy to bring signals together, to make decisions, and enforce organizational policies." -Icon "🔒" -IconColor "#0078d4" -Style "Standard" -ShadowIntensity 'Normal' -BorderRadius 2px

        # Conditional Access
        New-HTMLInfoCard -Title "Conditional Access" -Subtitle "Control user access based on Conditional Access policy to bring signals together, to make decisions, and enforce organizational policies." -IconSolid running -IconColor RedBerry -Style "Standard" -ShadowIntensity 'Normal' -BorderRadius 2px
    }


    # Additional services section
    New-HTMLSection -HeaderText 'Additional Services' {
        New-HTMLSection -Density Spacious {
            # Try Microsoft Entra admin center
            New-HTMLInfoCard -Title "Try Microsoft Entra admin center" -Subtitle "Secure your identity environment with Microsoft Entra ID, permissions management and more." -Icon "🔧" -IconColor "#0078d4" -Style "Standard" -ShadowIntensity 'Normal' -BorderRadius 2px

            # User Profile Card
            New-HTMLInfoCard -Title "Przemysław Klys" -Subtitle "e6a8f1cf-0874-4323-a12f-2bf51bb6dfdd | Global Administrator and 2 other roles" -Icon "👤" -IconColor "#6c757d" -Style "Standard" -ShadowIntensity 'Normal' -BorderRadius 2px

            # Secure Score
            New-HTMLInfoCard -Title "Secure Score for Identity" -Number "28.21%" -Subtitle "Secure score updates can take up to 48 hours." -Icon "🏆" -IconColor "#ffc107" -Style "Standard" -ShadowIntensity 'Normal' -BorderRadius 2px

            # Microsoft Entra Connect
            New-HTMLInfoCard -Title "Microsoft Entra Connect" -Number "✅ Enabled" -Subtitle "Last sync was less than 1 hour ago" -Icon "🔄" -IconColor "#198754" -Style "Standard" -ShadowIntensity 'Normal' -BorderRadius 2px
        }
    }

    # Enhanced styling showcase with different shadow intensities
    New-HTMLSection -HeaderText 'Enhanced Visual Showcase' {
        New-HTMLSection -Density Spacious {
            # ExtraNormal shadows for high-priority items
            New-HTMLInfoCard -Title "HIGH PRIORITY" -Number "Critical" -Subtitle "Maximum visibility shadow" -Icon "⚠" -IconColor "#dc3545" -ShadowIntensity 'Normal' -ShadowColor 'rgba(220, 53, 69, 0.4)' -BorderRadius 2px

            # Normal colored shadows
            New-HTMLInfoCard -Title "Security Alert" -Number "Active" -Subtitle "Normal red shadow for attention" -Icon "🔴" -IconColor "#dc3545" -ShadowIntensity 'Normal' -ShadowColor 'rgba(220, 53, 69, 0.3)' -BorderRadius 2px

            # Normal with custom color
            New-HTMLInfoCard -Title "Performance" -Number "Good" -Subtitle "Green shadow indicates success" -Icon "✅" -IconColor "#198754" -ShadowIntensity 'Normal' -ShadowColor 'rgba(25, 135, 84, 0.3)' -BorderRadius 2px

            # Custom shadow settings
            New-HTMLInfoCard -Title "Custom Styling" -Number "Advanced" -Subtitle "Custom blur and spread values" -Icon "🎨" -IconColor "#6f42c1" -ShadowIntensity 'Custom' -ShadowBlur 15 -ShadowSpread 3 -ShadowColor 'rgba(111, 66, 193, 0.25)' -BorderRadius 2px
        }
    }

} -FilePath "$PSScriptRoot\Example-MicrosoftEntra.html" -TitleText "Microsoft Entra Interface Recreation" -Online -Show

💡 Real-world Dashboard Use

Consider a practical dashboard showing Microsoft Entra details with enhanced visuals and layout control from my module GraphEssentials

Once you make the window smaller, or larger the sections content will shift one way or another

💡 Wrap-up

These new additions to PSWriteHTML greatly enhance your ability to create professional-grade HTML reports. All functionalities are intuitive, customizable, and aimed at saving time.

Explore further by updating your PSWriteHTML module and incorporating these features into your dashboards today. Share your experiences and feedback!


For more detailed examples and code snippets, check the PSWriteHTML GitHub repository.

💡 Installing or updating PowerShell module

PSWriteHTML is available in PowerShellGallery as an easy-to-use download.

Install-Module PSWriteHTML -Force -Verbose

Once the module is installed, just copy/paste examples and you're ready to go.

PS. Remember to save the file before running when using $PSScriptRoot or replace it with full path to make sure file has place to save.

The post Enhanced Dashboards with PSWriteHTML – Introducing InfoCards and Density Options appeared first on Evotec.

]]>
Mastering Active Directory Hygiene: Automating SIDHistory Cleanup with CleanupMonster https://evotec.pl/mastering-active-directory-hygiene-automating-sidhistory-cleanup-with-cleanupmonster/ Sun, 16 Mar 2025 17:47:45 +0000 https://evotec.xyz/?p=18858 Security Identifier (SID) History is a useful mechanism in Active Directory (AD) migrations. It allows users and groups in a new domain to retain access to resources that still rely on permissions from the old domain. However, once migrations are completed, these historical SIDs can become clutter, posing both security and administrative challenges. While it’s best to remove unnecessary SID History as soon as you’re done migrating, many environments skip this step. Over time, decommissioned or broken trusts make cleanup more difficult, and domain objects can accrue so many old entries that you lose track of what is still required.

The post Mastering Active Directory Hygiene: Automating SIDHistory Cleanup with CleanupMonster appeared first on Evotec.

]]>

Security Identifier (SID) History is a useful mechanism in Active Directory (AD) migrations. It allows users and groups in a new domain to retain access to resources that still rely on permissions from the old domain. However, once migrations are completed, these historical SIDs can become clutter, posing both security and administrative challenges. While it’s best to remove unnecessary SID History as soon as you’re done migrating, many environments skip this step. Over time, decommissioned or broken trusts make cleanup more difficult, and domain objects can accrue so many old entries that you lose track of what is still required.

💡 Why Clean Up SID History?

  1. Security – Old SID History entries can become an attack vector. If an external or stale SID is still lurking, it could theoretically be used to gain unauthorized access.
  2. Administrative Overhead – Carrying extensive historical SIDs creates noise in AD. Administrators might spend extra time digging through or troubleshooting old references.
  3. Compliance – Certain regulations or internal policies encourage or require minimal external references in production domains.

When left unattended for years, stale SID History can balloon into thousands of entries. At that point, a methodical, controlled cleanup is needed to avoid disruptions.

To address these challenges, CleanupMonster now offers a new function, Invoke-ADSIDHistoryCleanup, that provides a structured way to remove unwanted SID History:

  • Targeted or Bulk Removal – You can select specific domains, organizational units, or even specific domain SIDs to include or exclude. This fine-grained control ensures you only remove what you intend.
  • Safety Limits – Cleanup can be restricted to a certain number of objects (e.g., 2 per run) or a certain number of SID entries (e.g., 5 total) per execution. This way, if something goes wrong, the impact is contained.
  • Reporting and Logging – The function creates HTML and XML reports that let you review changes before, during, and after they happen. This includes which objects were modified, what SID values were removed, and potential errors.
  • WhatIf Mode – Testing your plan is a must. By using -WhatIf, you can simulate the removal process to see exactly what would be deleted without making any changes.

By running the process in small increments, you can monitor how removing SID History affects user access. If issues arise, you can restore or adjust your approach before continuing.

💡 Reporting Key to Success

Cleaning up SID History is pretty easy, however the key to doing it properly is to have a proper reporting and logging process that can help find out the root cause of user issues that surely will show up once you start removing thousands of SID History records. That's why CleanupMonster provides built-in HTML reporting and an email body prepared for this long cleanup process. It follows a similar pattern to the cleanup of computers and provides:

  • Overall report about SID History in a forest including all records, tabs with specific domain SIDs, and more
  • Current Run Tab – contains last run information about which objects were affected by the cleanup.
  • Historical Data Tab – contains history of one or more runs over time
  • Logs – each step is logged to file, but for the usefulness of the report, it's also included in HTML for easy access

The above image shows an overview of the current SID history in the forest. It tries to summarize data based on internal SID History (forest moves), External SID History (trusts), and Unknown SID history (deleted trusts, etc). The image below shows the removal of the SID History, which shows the SID from which the object was removed and how the object looked before and after the removal happened. It contains information about the action, when it happened, and whether it was successful.

The history tab contains information about all earlier runs of SID History Cleanup and what happened. Keep in mind that the XML file must be present, as the history is kept internally. Otherwise, the history will only keep current data.

The last tab, as shown in the image, contains logs from the script run. While the script writes the log to the file, it also displays its content if the HTML report is used inside TheDashboard.

Finally, every time the script runs, it prepares an HTML body for an email to be sent outside the script scope. Once the script runs, you can use the module's HTML body or create your own with available data.

Now that you know what you're getting, it's time to see how complicated the setup for the above is. Right?

💡 Running the script

The first step is to install the CleanupMonster module

Install-Module CleanupMonster -Force -Verbose

Then, it's a matter of running a single function, Invoke-ADSIDHistoryCleanup. Below is an example of how you can schedule a slow, controlled cleanup over time. Please notice the use of specific SIDHistoryDomains, RemoveLimits, and other settings.

# Prepare splat
$invokeADSIDHistoryCleanupSplat = @{
    Verbose                 = $true
    WhatIf                  = $true
    IncludeSIDHistoryDomain = @(
        # 'S-1-5-21-3661168273-3802070955-2987026695'
        'S-1-5-21-853615985-2870445339-3163598659'
    )
    RemoveLimitSID          = 1
    RemoveLimitObject       = 2
    SafetyADLimit           = 1
    ShowHTML                = $true
    Online                  = $true
    LogPath                 = "$PSScriptRoot\ProcessedSIDHistory.log"
    ReportPath              = "$PSScriptRoot\ProcessedSIDHistory.html"
    DataStorePath           = "$PSScriptRoot\ProcessedSIDHistory.xml"
}

# Run the script
$Output = Invoke-ADSIDHistoryCleanup @invokeADSIDHistoryCleanupSplat
$Output | Format-Table -AutoSize

# Lets send an email
$EmailBody = $Output.EmailBody

# Send email with Microsoft Graph and using Mailozaurr module
Connect-MgGraph -Scopes 'Mail.Send' -NoWelcome
Send-EmailMessage -To '[email protected]' -From '[email protected]' -MgGraphRequest -Subject "Automated SID Cleanup Report" -Body $EmailBody -Priority Low -Verbose

Once you run the script, an HTML report is generated, and you can also send an email with pre-prepared content. Remember that the function has multiple other parameters that help delete only what you want. Please DO NOT RUN the script without first testing it out on TEST ENVIRONMENT and understanding what happens and how it affects the environment! This function is DANGEROUS! Here's a help file for the function that cleans up SID History on a global level.

NAME
    Invoke-ADSIDHistoryCleanup

SYNOPSIS
    Cleans up SID history entries in Active Directory based on various filtering criteria.


SYNTAX
    Invoke-ADSIDHistoryCleanup [[-Forest] <String>] [[-IncludeDomains] <String[]>] [[-ExcludeDomains] <String[]>] [[-IncludeOrganizationalUnit]
    <String[]>] [[-ExcludeOrganizationalUnit] <String[]>] [[-IncludeSIDHistoryDomain] <String[]>] [[-ExcludeSIDHistoryDomain] <String[]>]
    [[-RemoveLimitSID] <Nullable`1>] [[-RemoveLimitObject] <Nullable`1>] [[-IncludeType] <String[]>] [[-ExcludeType] <String[]>] [[-ReportPath] <String>]
    [[-DataStorePath] <String>] [-ReportOnly] [[-LogPath] <String>] [[-LogMaximum] <Int32>] [-LogShowTime] [[-LogTimeFormat] <String>] [-Suppress]
    [-ShowHTML] [-Online] [-DisabledOnly] [[-SafetyADLimit] <Nullable`1>] [-DontWriteToEventLog] [-WhatIf] [-Confirm] [<CommonParameters>]


DESCRIPTION
    This function identifies and removes SID history entries from AD objects based on specified filters.
    It can target internal domains (same forest), external domains (trusted), or unknown domains.
    The function allows for detailed reporting before making any changes.


PARAMETERS
    -Forest <String>
        The name of the forest to process. If not specified, uses the current forest.

    -IncludeDomains <String[]>
        An array of domain names to include in the cleanup process.

    -ExcludeDomains <String[]>
        An array of domain names to exclude from the cleanup process.

    -IncludeOrganizationalUnit <String[]>
        An array of organizational units to include in the cleanup process.

    -ExcludeOrganizationalUnit <String[]>
        An array of organizational units to exclude from the cleanup process.

    -IncludeSIDHistoryDomain <String[]>
        An array of domain SIDs to include when cleaning up SID history.

    -ExcludeSIDHistoryDomain <String[]>
        An array of domain SIDs to exclude when cleaning up SID history.

    -RemoveLimitSID <Nullable`1>
        Limits the total number of SID history entries to remove.

    -RemoveLimitObject <Nullable`1>
        Limits the total number of objects to process for SID history removal. Defaults to 1 to prevent accidental mass deletions.

    -IncludeType <String[]>
        Specifies which types of SID history to include: 'Internal', 'External', or 'Unknown'.
        Defaults to all three types if not specified.

    -ExcludeType <String[]>
        Specifies which types of SID history to exclude: 'Internal', 'External', or 'Unknown'.

    -ReportPath <String>
        The path where the HTML report should be saved. Used with the -Report parameter.

    -DataStorePath <String>
        Path to the XML file used to store processed SID history entries.

    -ReportOnly [<SwitchParameter>]
        If specified, only generates a report without making any changes.

    -LogPath <String>
        The path to the log file to write.

    -LogMaximum <Int32>
        The maximum number of log files to keep.

    -LogShowTime [<SwitchParameter>]
        If specified, includes the time in the log entries.

    -LogTimeFormat <String>
        The format to use for the time in the log entries.

    -Suppress [<SwitchParameter>]
        Suppresses the output of the function and only returns the summary information.

    -ShowHTML [<SwitchParameter>]
        If specified, shows the HTML report in the default browser.

    -Online [<SwitchParameter>]
        If specified, uses online resources in HTML report (CSS/JS is loaded from CDN). Otherwise local resources are used (bigger HTML file).

    -DisabledOnly [<SwitchParameter>]
        Only processes objects that are disabled.

    -SafetyADLimit <Nullable`1>
        Stops processing if the number of objects with SID history in AD is less than the specified limit.

    -DontWriteToEventLog [<SwitchParameter>]

    -WhatIf [<SwitchParameter>]
        Shows what would happen if the function runs. The SID history entries aren't actually removed.

    -Confirm [<SwitchParameter>]

    <CommonParameters>
        This cmdlet supports the common parameters: Verbose, Debug,
        ErrorAction, ErrorVariable, WarningAction, WarningVariable,
        OutBuffer, PipelineVariable, and OutVariable. For more information, see
        about_CommonParameters (https:/go.microsoft.com/fwlink/?LinkID=113216).

    -------------------------- EXAMPLE 1 --------------------------

    PS C:\>Invoke-ADSIDHistoryCleanup -Forest "contoso.com" -IncludeType "External" -ReportOnly -ReportPath "C:\Temp\SIDHistoryReport.html" -WhatIf

    Generates a report of external SID history entries in the contoso.com forest without making any changes.




    -------------------------- EXAMPLE 2 --------------------------

    PS C:\>Invoke-ADSIDHistoryCleanup -IncludeDomains "domain1.local" -IncludeType "Internal" -RemoveLimitSID 2 -WhatIf

    Removes up to 2 internal SID history entries from objects in domain1.local.




    -------------------------- EXAMPLE 3 --------------------------

    PS C:\>Invoke-ADSIDHistoryCleanup -ExcludeSIDHistoryDomain "S-1-5-21-1234567890-1234567890-1234567890" -WhatIf -RemoveLimitObject 2

    Shows what SID history entries would be removed while excluding entries from the specified domain SID. Limits the number of objects to process to 2.




    -------------------------- EXAMPLE 4 --------------------------

    PS C:\># Prepare splat

    $invokeADSIDHistoryCleanupSplat = @{
        Verbose                 = $true
        WhatIf                  = $true
        IncludeSIDHistoryDomain = @(
            'S-1-5-21-3661168273-3802070955-2987026695'
            'S-1-5-21-853615985-2870445339-3163598659'
        )
        IncludeType             = 'External'
        RemoveLimitSID          = 1
        RemoveLimitObject       = 2

        SafetyADLimit           = 1
        ShowHTML                = $true
        Online                  = $true
        DisabledOnly            = $true
        #ReportOnly              = $true
        LogPath                 = "C:\Temp\ProcessedSIDHistory.log"
        ReportPath              = "$PSScriptRoot\ProcessedSIDHistory.html"
        DataStorePath           = "$PSScriptRoot\ProcessedSIDHistory.xml"
    }

    # Run the script
    $Output = Invoke-ADSIDHistoryCleanup @invokeADSIDHistoryCleanupSplat
    $Output | Format-Table -AutoSize

    # Lets send an email
    $EmailBody = $Output.EmailBody

    Connect-MgGraph -Scopes 'Mail.Send' -NoWelcome
    Send-EmailMessage -To '[email protected]' -From '[email protected]' -MgGraphRequest -Subject "Automated SID Cleanup Report" -Body
    $EmailBody -Priority Low -Verbose




REMARKS
    To see the examples, type: "get-help Invoke-ADSIDHistoryCleanup -examples".
    For more information, type: "get-help Invoke-ADSIDHistoryCleanup -detailed".
    For technical information, type: "get-help Invoke-ADSIDHistoryCleanup -full".

Many organizations prefer not to remove all SID History at once. With small, incremental runs, you can do the following:

  • Schedule the cleanup (e.g., daily, weekly)
  • Limit the number of objects or SIDs each time
  • Review the report and logs
  • Confirm that users can still access essential resources
  • Increase or decrease cleanup speed as confidence grows

This approach helps build trust in the process and reduces the risk of widespread access issues.

💡 Warnings and Best Practices

  • Always test with WhatIf – Verify the objects and SID values targeted before any real deletions.
  • Use RemoveLimitObject and RemoveLimitSID to make sure a limited number of SIDs are removed/gets affected (default one object being processed)
  • Backup – Ensure you have AD backups or ways to revert changes if you discover certain SID History was still required.
  • Communication—Inform your help desk or relevant teams that SID History entries are being removed so they can quickly troubleshoot access issues.
  • Prefer Early Cleanup – If you’re in the middle of a domain migration, do not postpone SID History cleanup indefinitely. Doing it when trusts are still available is vastly easier than waiting years.

💡 Conclusion

Cleaning up SID History is an important but often forgotten step. By leveraging CleanupMonster’s Invoke-ADSIDHistoryCleanup, you have a safer path to gradually remove stale SID entries, maintain a cleaner Active Directory, and strengthen security. Test thoroughly, proceed incrementally, and preserve your logs for audit and troubleshooting.

For more information on how SID History works, you can review Microsoft’s documentation on SID History. The new feature is part of the CleanupMonster module, which handles tasks like cleaning up unused computer accounts. Check out the existing blog posts on CleanupMonster for details about its other functionalities.

While CleanupMonster is a one-shot solution to SID History Removal, remember that you should know what you're doing before even starting this process. It's there for a reason, and removing it, as proposed by CleanupMonster, has its consequences. This solution is mainly built when it's almost impossible to tell what is what anymore, and cleaning it up correctly is simply impossible. Additionally, please do not run this module if you're only after the SID History report. While the module has a ReportOnly switch, running a dedicated reporting command is better. You can get the SID history report from the  ADEssentials PowerShell module.

# Install module or update if nessecary
Install-Module ADEssentials -Force -Verbose

# Run the report
$Object = Show-WinADSIDHistory -Online -PassThru
$Object

The commands above deliver very similar output to the one shown in the screenshots above without the risk of running the cleanup tool. It's the preferred method of establishing if you have a problem that this tool tries to solve!

The post Mastering Active Directory Hygiene: Automating SIDHistory Cleanup with CleanupMonster appeared first on Evotec.

]]>
Upgrade Azure Active Directory Connect fails with unexpected error https://evotec.pl/upgrade-azure-active-directory-connect-fails-with-unexpected-error/ Wed, 02 Oct 2024 12:17:34 +0000 https://evotec.xyz/?p=18732 Today, I made the decision to upgrade my test environment and update the version of Azure AD Connect to the latest one. The process is usually simple: download a new MSI, run it, click next a few times, enter the credentials for your Global Admin, and you're finished. However, this time, I encountered an error.

The post Upgrade Azure Active Directory Connect fails with unexpected error appeared first on Evotec.

]]>

Today, I made the decision to upgrade my test environment and update the version of Azure AD Connect to the latest one. The process is usually simple: download a new MSI, run it, click next a few times, enter the credentials for your Global Admin, and you're finished. However, this time, I encountered an error.

💡 Problem description

Unable to validate credentials due to an unexpected error. Restart Azure AD Connect with the /InteractiveAuth option to further diagnose this issue. (extendedMessage: An error occurred while sending the request. | The underlying connection was closed: An unexpected error occurred on a send. | Unable to read data from the transport connection: An existing connection was forcibly closed by the remote host. | An existing connection was forcibly closed by the remote host webException: The underlying connection was closed: An unexpected error occurred on a send. STS endpoint: HTTPS://LOGIN.MICROSOFTONLINE.COM/XXXXXXX)

💡 Solution

At first, I thought the solution to this problem was quite simple—due to authentication changes, one needs to start the upgrade process with InteractiveAuth, as it will most likely require MFA. So I restarted the process, this time with the InteractiveAuth switch, and to my surprise, it didn't work!

"C:\Program Files\Microsoft Azure Active Directory Connect\AzureADConnect.exe" /interactiveauth

Since I was at the same place as before, I've decided to try to make sure I use TLS 1.2 on the server. It seems all the new services require TLS 1.2, and if you've been using your server for a while, that's not a default setting. I had a similar problem four years ago when PowerShellGallery was broken due to the TLS 1.2 change I blogged about. You can read about it here. Setting proper registry settings the way to go:

Set-ItemProperty `
    -Path "HKLM:\SOFTWARE\Microsoft\.NetFramework\v4.0.30319" `
    -Name "SchUseStrongCrypto" `
    -Value "1" `
    -Type DWord `
    -Force
Set-ItemProperty `
    -Path "HKLM:\SOFTWARE\Wow6432Node\Microsoft\.NetFramework\v4.0.30319" `
    -Name "SchUseStrongCrypto" `
    -Value "1" `
    -Type DWord `
    -Force

Now reboot the server, restart the upgrade process.

This time, the error is a bit different, so again, I suspect it's related to the MFA process on my account. Let's restart it again with InteractiveAuth.

As expected, it worked much better this time, prompting me to change my password.

Once changed, authentication worked and I was ready for an upgrade!

The post Upgrade Azure Active Directory Connect fails with unexpected error appeared first on Evotec.

]]>
Mastering Active Directory Hygiene: Automating Stale Computer Cleanup with CleanupMonster https://evotec.pl/mastering-active-directory-hygiene-automating-stale-computer-cleanup-with-cleanupmonster/ Sun, 25 Aug 2024 13:14:39 +0000 https://evotec.xyz/?p=18694 Have you ever looked at your Active Directory and wondered, "Why do I still have computers listed that haven't been turned on since World Cup 2016?" Yeah, we've all been there. Keeping AD clean and up-to-date is like trying to organize your garage—it’s easy to put off until it becomes a total mess.

The post Mastering Active Directory Hygiene: Automating Stale Computer Cleanup with CleanupMonster appeared first on Evotec.

]]>

Have you ever looked at your Active Directory and wondered, „Why do I still have computers listed that haven't been turned on since World Cup 2016?” Yeah, we've all been there. Keeping AD clean and up-to-date is like trying to organize your garage—it’s easy to put off until it becomes a total mess.

That’s where my PowerShell module, CleanupMonster, comes to the rescue. This little powerhouse is designed to help you effortlessly track down and deal with those old, stale computers cluttering your directory. Whether you want to move, disable, or even give them the boot, CleanupMonster has your back. And it doesn’t stop there—if you need to gather some intel from Entra ID (Azure AD), Intune, or Jamf to make an informed decision, CleanupMonster can handle that, too.

In this post, I'll explain how CleanupMonster can help you clean up your Active Directory without breaking a sweat. Let’s dive in and give your AD the spring cleaning it deserves!

💡 Cleanup your Active Directory

The CleanupMonster PowerShell module was created as an easy way to remove stale computer objects from the Active Directory. However, each company and enterprise has rules regarding what happens to computers and how they should be handled. Then, there is a question of how to tell the computer that it is a stale object. While there are many solutions on the internet, if you look hard enough, CleanupMonster does this in a way that nothing comes close!

There are several ways to determine when a computer in Active Directory becomes inactive. However, the most common methods are LastLogonDate and PasswordLastSet, which should ideally not exceed 30 days. Some people might advise you to use simple PowerShell commands to locate and delete these inactive computers – it can be done in just five lines of code!

Get-ADComputer -filter * -Proper Name, DisplayName, SamAccountName, lastlogondate, PasswordLastSet | ft Name, DisplayName, SamAccountName, lastlogondate, PasswordLastSet

It's true that if you are lucky enough to have a small organization with only a few attributes to consider, you can make informed decisions and maintain a clean Active Directory without using CleanupMonster. However, things get more complicated for larger companies or enterprises. In these cases, there are VPN solutions, employees who haven't connected to the network for months, and those who work on Mac devices that may not get updated regularly. What if a computer object was just prepared for onboarding, but the cleanup script mistakenly identified it as inactive and deleted it? There are many potential issues, which can lead to employees opening tickets and reporting that they can't work anymore.

This is where CleanupMonster comes in. It's a PowerShell module with a single command called Invoke-ADComputersCleanup. Its multiple parameters allow it to be configured in many ways, meeting the most advanced scenarios you can imagine.

💡 Features & Options

CleanupMonster has the following features:

  • Ability to disable, disable and move, move and disable, move or delete computers
  • All five actions from above can have different rules when a given task happens
  • It's able to check when the object was created and prevent the deletion of objects younger than X days
  • It's able to check LastLogonDate and LastPasswordSet and requires it to be certain days old to consider for disabling, moving, or delete
  • If LastLogonDate or LastPasswordSet is empty, it is treated as never used; therefore, it applies the maximum number of days to it.
  • It can check Intune data for LastLogonDate for a given computer, providing the ability to improve data with cloud data for those non-connected machines.
  • It can check Entra ID data (Azure AD) for LastLogonDate for a given computer to improve the assessment of deletion.
  • It's able to check Jamf PRO for LastLogonDate for macOS devices.
  • You can target whole forest, or include/exclude specific domains from this process

This means you can configure it to only disable, move, or delete an object if LastLogonDate is over 90 days, LastPasswordDate is over 90 days, LastLogonDate in Azure AD is over 90 days, LastLogonDate in Intune is over 90 days, and LastLogonDate is over 90 days in Jamf PRO, and additional that the object was created over 90 days ago. This can be further enhanced to action an object based on OperatingSystem (include, exclude) and exclude certain Organizational Units or specific objects wholly based on their Name or DistinguishedName. All of this is configurable with just an option on the cmdlet. Finally, there's one more option called the Pending Deletion list. The pending deletion list keeps track of disabled/moved objects to know how long ago they were disabled/moved so that computer objects can be deleted. This means you can configure CleanupMonster to turn off an object but tell it to wait 90 days before deleting it. If the object gets reenabled during that time, it's automatically removed from the pending list, and the time timer starts again.

If that wasn't enough the module has:

  • Built-in HTML reporting keeping track of what was actioned, what will be actioned, what is on pending deletion list, including logs from each action
  • It's able to write to Event Log when the computer is actioned so it can be tracked in SIEM
  • It has builtin DisableLimit, DeleteLimit that allow only X number of objects actioned per run, preventing abuse or accidental deletions.
  • It provides Safety limits such as SafetyADLimit,SafetyAzureADLimit, SafetyIntuneLimit, SafetyJamfLimit that the script requires certain number of objects to be gathred before continuing. This is to prevent decision making process based on incomplete data because something went wrong during gathering data phase (lack of internet, certain server being down etc).
  • Provides WhatIf functionality to Disabling, Move or Deleting together or separately
  • Full logging of all actions to file

Additionally, with just a few lines of code, you can send daily results to email using PSWriteHTML functionality.

💡 Example configuration #1

I don't want to bore you too much with text, so let's jump into how some of the configurations look like:

# Run the script
$Configuration = @{
    Disable                        = $true
    DisableNoServicePrincipalName  = $null
    DisableIsEnabled               = $true
    DisableLastLogonDateMoreThan   = 90
    DisablePasswordLastSetMoreThan = 90
    DisableExcludeSystems          = @(
        # 'Windows Server*'
    )
    DisableIncludeSystems          = @()
    DisableLimit                   = 2 # 0 means unlimited, ignored for reports
    DisableModifyDescription       = $false
    DisableModifyAdminDescription  = $true

    Delete                         = $true
    DeleteIsEnabled                = $false
    DeleteNoServicePrincipalName   = $null
    DeleteLastLogonDateMoreThan    = 180
    DeletePasswordLastSetMoreThan  = 180
    DeleteListProcessedMoreThan    = 90 # 90 days since computer was added to list
    DeleteExcludeSystems           = @(
        # 'Windows Server*'
    )
    DeleteIncludeSystems           = @(

    )
    DeleteLimit                    = 2 # 0 means unlimited, ignored for reports

    Exclusions                     = @(
        '*OU=Domain Controllers*'
        '*OU=Servers,OU=Production*'
        'EVOMONSTER$'
        'EVOMONSTER.AD.EVOTEC.XYZ'
    )

    Filter                         = '*'
    WhatIfDisable                  = $true
    WhatIfDelete                   = $true
    LogPath                        = "$PSScriptRoot\Logs\DeleteComputers_$((Get-Date).ToString('yyyy-MM-dd_HH_mm_ss')).log"
    DataStorePath                  = "$PSScriptRoot\DeleteComputers_ListProcessed.xml"
    ReportPath                     = "$PSScriptRoot\Reports\DeleteComputers_$((Get-Date).ToString('yyyy-MM-dd_HH_mm_ss')).html"
    ShowHTML                       = $true
}

# Run one time as admin: Write-Event -ID 10 -LogName 'Application' -EntryType Information -Category 0 -Message 'Initialize' -Source 'CleanupComputers'
$Output = Invoke-ADComputersCleanup @Configuration
$Output

When run the script will disable object only if it matches last logon date over 90 days and last password date over 90 days and the object will be enabled. It will also disable maximum of 2 objects and modify admin description of the object with information about details of this action. However, to delete an object, it will require 180 days for the last logon and last password date and be on the processing list for at least 90 days. It will also delete only two objects per run. It won't touch objects in Domain Controllers OU, in Servers OU and it will ignore EVOMonster device. Since both WhatIfDelete and WhatIfDisable are enabled it will not do any real action and it will instead just display what it would do, doing no harm to AD. Finally it will generate HTML report to given path and display it in your favourite browser.

💡 Example configuration #2

Of course, we can make a more complicated example which, as part of disabling, also moves the computer. You can choose the order DisableAndMove or MoveAndDisable depending on which permissions you have granted for your GMSA account. You can also define the path per domain where the computers are moved when required. There's also the ability to treat moving as a separate process: Disable/Move/Delete, rather than doing Disable & Move / Delete. As you can see in the example below, some commented-out settings show you unused but available options.

# connect to graph for Email sending
Connect-MgGraph -Scopes Mail.Send -NoWelcome

$invokeADComputersCleanupSplat = @{
    #ExcludeDomains                      = 'ad.evotec.xyz'
    # safety limits (minimum amount of computers that has to be returned from each source)
    SafetyADLimit                       = 30
    #SafetyAzureADLimit                  = 5
    #SafetyIntuneLimit                   = 3
    #SafetyJamfLimit                     = 50
    # disable settings
    Disable                             = $true
    DisableAndMove                      = $true
    DisableAndMoveOrder                 = 'DisableAndMove' # DisableAndMove, MoveAndDisable
    #DisableIsEnabled                    = $true
    DisableLimit                        = 1
    DisableLastLogonDateMoreThan        = 90
    DisablePasswordLastSetMoreThan      = 90
    #DisableLastSeenAzureMoreThan        = 90
    DisableRequireWhenCreatedMoreThan       = 90

    DisablePasswordLastSetOlderThan     = Get-Date -Year 2023 -Month 1 -Day 1
    #DisableLastSyncAzureMoreThan   = 90
    #DisableLastContactJamfMoreThan = 90
    #DisableLastSeenIntuneMoreThan       = 90
    DisableMoveTargetOrganizationalUnit = @{
        'ad.evotec.xyz' = 'OU=Disabled,OU=Computers,OU=Devices,OU=Production,DC=ad,DC=evotec,DC=xyz'
        'ad.evotec.pl'  = 'OU=Disabled,OU=Computers,OU=Devices,OU=Production,DC=ad,DC=evotec,DC=pl'
    }

    # move settings
    Move                                = $false
    MoveLimit                           = 1
    MoveLastLogonDateMoreThan           = 90
    MovePasswordLastSetMoreThan         = 90
    #MoveLastSeenAzureMoreThan    = 180
    #MoveLastSyncAzureMoreThan    = 180
    #MoveLastContactJamfMoreThan  = 180
    #MoveLastSeenIntuneMoreThan   = 180
    #MoveListProcessedMoreThan    = 90 # disabled computer has to spend 90 days in list before it can be deleted
    MoveIsEnabled                       = $false # Computer has to be disabled to be moved
    MoveTargetOrganizationalUnit        = @{
        'ad.evotec.xyz' = 'OU=Disabled,OU=Computers,OU=Devices,OU=Production,DC=ad,DC=evotec,DC=xyz'
        'ad.evotec.pl'  = 'OU=Disabled,OU=Computers,OU=Devices,OU=Production,DC=ad,DC=evotec,DC=pl'
    }

    # delete settings
    Delete                              = $false
    DeleteLimit                         = 2
    DeleteLastLogonDateMoreThan         = 180
    DeletePasswordLastSetMoreThan       = 180
    #DeleteLastSeenAzureMoreThan         = 180
    #DeleteLastSyncAzureMoreThan    = 180
    #DeleteLastContactJamfMoreThan  = 180
    #DeleteLastSeenIntuneMoreThan   = 180
    #DeleteListProcessedMoreThan    = 90 # disabled computer has to spend 90 days in list before it can be deleted
    DeleteIsEnabled                     = $false # Computer has to be disabled to be deleted
    # global exclusions
    Exclusions                          = @(
        '*OU=Domain Controllers*' # exclude Domain Controllers
    )
    # filter for AD search
    Filter                              = '*'
    # logs, reports and datastores
    LogPath                             = "$PSScriptRoot\Logs\CleanupComputers_$((Get-Date).ToString('yyyy-MM-dd_HH_mm_ss')).log"
    DataStorePath                       = "$PSScriptRoot\CleanupComputers_ListProcessed.xml"
    ReportPath                          = "$PSScriptRoot\Reports\CleanupComputers_$((Get-Date).ToString('yyyy-MM-dd_HH_mm_ss')).html"
    # WhatIf settings
    ReportOnly                          = $false
    WhatIfDisable                       = $true
    WhatIfMove                          = $true
    WhatIfDelete                        = $true
    ShowHTML                            = $true

    DontWriteToEventLog                 = $true
}

$Output = Invoke-ADComputersCleanup @invokeADComputersCleanupSplat

# Now lets send email using Graph
[Array] $DisabledObjects = $Output.CurrentRun | Where-Object { $_.Action -eq 'Disable' }
[Array] $DeletedObjects = $Output.CurrentRun | Where-Object { $_.Action -eq 'Delete' }

$EmailBody = EmailBody -EmailBody {
    EmailText -Text "Hello,"

    EmailText -LineBreak

    EmailText -Text "This is an automated email from Automations run on ", $Env:COMPUTERNAME, " on ", (Get-Date -Format 'yyyy-MM-dd HH:mm:ss'), " by ", $Env:UserName -Color None, Green, None, Green, None, Green -FontWeight normal, bold, normal, bold, normal, bold

    EmailText -LineBreak

    EmailText -Text "Following is a summary for the computer object cleanup:" -FontWeight bold
    EmailList {
        EmailListItem -Text "Objects actioned: ", $Output.CurrentRun.Count -Color None, Green -FontWeight normal, bold
        EmailListItem -Text "Objects deleted: ", $DeletedObjects.Count -Color None, Salmon -FontWeight normal, bold
        EmailListItem -Text "Objects disabled: ", $DisabledObjects.Count -Color None, Orange -FontWeight normal, bold
    }

    EmailText -Text "Following objects were actioned:" -LineBreak -FontWeight bold -Color Salmon
    EmailTable -DataTable $Output.CurrentRun -HideFooter {
        New-HTMLTableCondition -Name 'Action' -ComparisonType string -Value 'Delete' -BackGroundColor PinkLace -Inline
        New-HTMLTableCondition -Name 'Action' -ComparisonType string -Value 'Disable' -BackGroundColor EnergyYellow -Inline
        New-HTMLTableCondition -Name 'ActionStatus' -ComparisonType string -Value 'True' -BackGroundColor LightGreen -Inline
        New-HTMLTableCondition -Name 'ActionStatus' -ComparisonType string -Value 'False' -BackGroundColor Salmon -Inline
        New-HTMLTableCondition -Name 'ActionStatus' -ComparisonType string -Value 'Whatif' -BackGroundColor LightBlue -Inline
    }

    EmailText -LineBreak

    EmailText -Text "Regards,"
    EmailText -Text "Automations Team" -FontWeight bold
}

# send email using Mailozaurr
Send-EmailMessage -To '[email protected]' -From '[email protected]' -MgGraphRequest -Subject "Automated Computer Cleanup Report" -Body $EmailBody -Priority Low -Verbose -WhatIf

At the bottom of it, you can see how I'm using PSWriteHTML along with the Mailozaurr module, which, based on the module's output, sends me nicely formatted emails.

💡 Fancy reporting & logging

One of the essential parts of any good solution is knowing what happens when it happens and having some option to go back in time and assess the situation. CleanupMonster provides an advanced reporting engine that allows you to view the following:

  • What happened during the run time? Which objects were touched, and what parameters they had
  • Devices history showing the history of script actions over time to look back 1-2-10 months back
  • Devices are on the pending list for deletion, so you can see how many objects are waiting to be purged according to the rules you've used.
  • All devices have a full assessment based on the provided rules, allowing you to assess how many devices require deletion and are still suitable.

All reports are visually attractive, so IT Directors/Managers can have a single view of all computer objects.

Every action the script takes or has taken in the past is accessible and can be used as evidence.

💡 Installing or updating PowerShell module

CleanupMonster is available in PowerShellGallery as an easy-to-use download.

Install-Module CleanupMonster -Force -Verbose

If you need Jamf Pro functionality, you also need to install

Install-Module PowerJamf -Force -Verbose

If you need Azure AD (Entra ID) and Intune functionality, you also need to install

Install-Module GraphEssentials -Force -Verbose

Once the module is installed, you can adjust it to your needs. Please remember to use WhatIfDisable, WhatIfMove, WhatIfDelete, or WhatIf (which is pretendable for everyone). Also, make sure that DeleteLimit, DisableLimit, and MoveLimit start small 1-2-10 objects for the first time so you can quickly recover from the wrong configuration and go from there. Finally, Safety* properties help you ensure any timeouts when getting data won't ruin your AD. Make sure to use them carefully!

The post Mastering Active Directory Hygiene: Automating Stale Computer Cleanup with CleanupMonster appeared first on Evotec.

]]>
Active Directory Replication Summary to your Email or Microsoft Teams https://evotec.pl/active-directory-replication-summary-to-your-email/ Wed, 17 Apr 2024 19:25:32 +0000 https://evotec.xyz/?p=18630 Active Directory replication is a critical process that ensures the consistent and up-to-date state of directory information across all domain controllers in a domain. Monitoring this process is important as it helps identify any issues that may arise and resolve them quickly. One way to monitor Active Directory replication is by using the Repadmin command-line tool. Repadmin provides a wealth of information about the replication status and health of a domain. However, manually checking the Repadmin output can be time-consuming and tedious, and running it manually every 30 minutes just to check if everything is great doesn't seem like a great idea. While PowerShell has its own commands around replication I've not found something as fast and reliable as repadmin /replsummary.

The post Active Directory Replication Summary to your Email or Microsoft Teams appeared first on Evotec.

]]>

Active Directory replication is a critical process that ensures the consistent and up-to-date state of directory information across all domain controllers in a domain. Monitoring this process is important as it helps identify any issues that may arise and resolve them quickly. One way to monitor Active Directory replication is by using the Repadmin command-line tool. Repadmin provides a wealth of information about the replication status and health of a domain. However, manually checking the Repadmin output can be time-consuming and tedious, and running it manually every 30 minutes just to check if everything is great doesn't seem like a great idea. While PowerShell has its own commands around replication I've not found something as fast and reliable as repadmin /replsummary.

💡 Replication Summary to an email

So, as part of my advanced Active Directory module called ADEssentials, I wrote a function that uses repadmin to generate results. Then, I use PSWriteHTML to process them and Mailozaurr to send them to my email. Here's a how I did it:

$ReplicationSummary = Get-WinADForestReplicationSummary -IncludeStatisticsVariable Statistics

$Body = EmailBody {
    EmailImage -Source 'https://evotec.xyz/wp-content/uploads/2021/04/Logo-evotec-bb.png' -UrlLink '' -AlternativeText 'Logo' -Width 181 -Heigh 57 -Inline

    EmailText -Text "Dear ", "AD Team," -LineBreak
    EmailText -Text "Upon reviewing the resuls of replication I've found: "
    EmailList {
        EmailListItem -Text "Servers with good replication: ", $($Statistics.Good) -Color Black, SpringGreen -FontWeight normal, bold
        EmailListItem -Text "Servers with replication failures: ", $($Statistics.Failures) -Color Black, Red -FontWeight normal, bold
        EmailListItem -Text "Servers with replication delta over 24 hours: ", $($Statistics.DeltaOver24Hours) -Color Black, Red -FontWeight normal, bold
        EmailListItem -Text "Servers with replication delta over 12 hours: ", $($Statistics.DeltaOver12Hours) -Color Black, Red -FontWeight normal, bold
        EmailListItem -Text "Servers with replication delta over 6 hours: ", $($Statistics.DeltaOver6Hours) -Color Black, Red -FontWeight normal, bold
        EmailListItem -Text "Servers with replication delta over 3 hours: ", $($Statistics.DeltaOver3Hours) -Color Black, Red -FontWeight normal, bold
        EmailListItem -Text "Servers with replication delta over 1 hour: ", $($Statistics.DeltaOver1Hours) -Color Black, Red -FontWeight normal, bold
        EmailListItem -Text "Unique replication errors: ", $($Statistics.UniqueErrors.Count) -Color Black, Red -FontWeight normal, bold
    }

    if ($Statistics.UniqueErrors.Count -gt 0) {
        EmailText -Text "Unique replication errors:"
        EmailList {
            foreach ($ErrorText in $Statistics.UniqueErrors) {
                EmailListItem -Text $ErrorText
            }
        }
    } else {
        EmailText -Text "It seems you're doing a great job! Keep it up! 😊" -LineBreak
    }

    EmailText -Text "For more details please check the table below:"

    EmailTable -DataTable $ReplicationSummary {
        EmailTableCondition -Inline -Name "Fail" -HighlightHeaders 'Fails', 'Total', 'PercentageError' -ComparisonType number -Operator gt 0 -BackgroundColor Salmon -FailBackgroundColor SpringGreen
    } -HideFooter

    EmailText -LineBreak
    EmailText -Text "Kind regards,"
    EmailText -Text "Your automation friend"
}


$EmailSplat = @{
    From           = '[email protected]'
    To             = '[email protected]'
    Body           = $Body
    Priority       = if ($Statistics.Failures -gt 0) { 'High' } else { 'Low' }
    Subject        = 'Replication Results 💖'
    Verbose        = $true
    WhatIf         = $false
    MgGraph        = $true
}

Connect-MgGraph
Send-EmailMessage @EmailSplat

What is the result of those 50 lines of code?

The Get-WinADForestReplicationSummary function joins over 100 functions available for Active Directory admins in the ADEssentials module. It's doing the heavy lifting of reading repadmin data and converting it to PowerShell objects. Then, we use the PSWriteHTML EmailBody function, which allows for the accessible building of emails without knowing HTML and CSS. Finally, since I wanted to send an email with Microsoft Graph, I've used Mailozaurr's amazing Send-EmailMessage to send an email.

What if you don't like emails? How about Microsoft Teams?

New-AdaptiveCard -Uri $TeamsUri {
    New-AdaptiveColumnSet {
        New-AdaptiveColumn -Width auto {
            New-AdaptiveImage -Url "https://evotec.xyz/wp-content/uploads/2021/04/Logo-evotec-bb.png" -Size Large -Style default
        }
        New-AdaptiveColumn -Width stretch {
            New-AdaptiveTextBlock -Text "Replication Summary" -Weight Bolder -Wrap
            if ($Statistics.Failures -gt 0) {
                $Summary = "There are $($Statistics.Failures) servers with replication issues. Please take a look and fix ASAP."
            } else {
                $Summary = "All servers are in good shape. Keep up the good work!"
            }
            New-AdaptiveTextBlock -Text $Summary -Subtle -Spacing None -Wrap
        }
    }
    New-AdaptiveContainer {
        New-AdaptiveTextBlock -Text "ad.evotec.pl" -Size Medium -Wrap
        New-AdaptiveTextBlock -Text "" -Subtle -Spacing None -Wrap
        New-AdaptiveTextBlock -Text (Get-Date)
    }
    New-AdaptiveContainer {
        New-AdaptiveColumnSet {
            New-AdaptiveColumn {
                New-AdaptiveTextBlock -Text "▲ $($Statistics.Good) servers with good replication" -Color Good -Spacing None
                New-AdaptiveTextBlock -Text "▼ $($Statistics.Failures) servers with failing replication" -Color Attention -Spacing None
            } -Width Stretch
            New-AdaptiveColumn {
                New-AdaptiveFactSet {
                    foreach ($Entry in $Statistics.GetEnumerator() | Select-Object -Skip 1 -Last 5) {
                        New-AdaptiveFact -Title $Entry.Key -Value $Entry.Value
                    }
                }
            } -Width Auto
        }
    } -Spacing None
} -FullWidth

And what do you get? Nice and fancy replication summary in Teams 😊

💡 What do I need?

To get it up and running you just need to:

Install-Module PSWriteHTML -Force -Verbose
Install-Module ADEssentials -Force -Verbose
# if you like emails
Install-Module Mailozaurr -Force -Verbose

# if you like teams
Install-Module PSTeams -Force -Verbose

Once modules are installed, you only modify the email body to suit your needs and send an email splat with the proper parameters, as your email provider requires. Alternatively, you can change the team's template or use it as is via Teams Incoming Webhooks. If you need more details on how to configure PSTeams, Mailozaurr, or use email-building functionality with PSWriteHTML, I invite you to search via multiple blogs that cover this functionality.

The post Active Directory Replication Summary to your Email or Microsoft Teams appeared first on Evotec.

]]>
Syncing Global Address List (GAL) to personal contacts and between Office 365 tenants with PowerShell https://evotec.pl/syncing-global-address-list-gal-to-personal-contacts-and-between-office-365-tenants-with-powershell/ Sun, 03 Dec 2023 15:32:13 +0000 https://evotec.xyz/?p=18406 Hey there! Today, I wanted to introduce you to one of the small but excellent module I've created called the O365Synchronizer. This module focuses on synchronizing contacts and users. If you've ever been tasked with synchronizing Global Address Lists (GAL) across different Office 365 tenants or just wanted to sync GAL with user mailboxes so they can access contacts directly on their phones, this tool is for you.

The post Syncing Global Address List (GAL) to personal contacts and between Office 365 tenants with PowerShell appeared first on Evotec.

]]>

Hey there! Today, I wanted to introduce you to one of the small but excellent module I've created called the O365Synchronizer. This module focuses on synchronizing contacts and users. If you've ever been tasked with synchronizing Global Address Lists (GAL) across different Office 365 tenants or just wanted to sync GAL with user mailboxes so they can access contacts directly on their phones, this tool is for you.

Think of O365Synchronizer as your new best friend in Office 365 synchronization needs. It's like having a magic wand that smoothly aligns your contact lists across various domains and even directly into user inboxes.

While several tools on the market do similar stuff, I was tempted to write my own. I hope you enjoy it!

O365Synchronizer aims to close two problems that I've encountered when working with Office 365:

  • Synchronizing Users and contacts to personal mailboxes to allow them to be visible on mobile phones without the necessity to go through GAL
  • Synchronizing Users as contacts between tenants (Tenant A gets users as contacts in Tenant B)

Most of the time, when you want to achieve either of those two, you have to resort to paid solutions. While I have nothing against the paid solutions (and I would like to get paid myself), it's a bit expensive for what it's needed for, in my honest opinion.

💡 Synchronizing members and contacts to user personal contacts using Sync-O365PersonalContact

O365Synchronizer utilizes Microsoft Graph API to get the users/contacts from Office 365 tenants and then pushes them using Microsoft Graph API to the user mailbox as contacts. Once Contacts are created, when the command is rerun, it compares existing Contacts for any changes and updates them if necessary. If the user gets removed from the tenant and is no longer on the source, it will also be removed from the user's mailbox. By default, the Sync-O365PersonalContact command uses GUID to distinguish contacts created by it from existing user contacts. It uses it to track what contacts it adds and only manages those, leaving existing user contacts untouched.

$ClientID = '9e1b3c'
$TenantID = 'ceb371f6'
$ClientSecret = 'nQF8Q'

# connect to Microsoft Graph API
$Credentials = [pscredential]::new($ClientID, (ConvertTo-SecureString $ClientSecret -AsPlainText -Force))
Connect-MgGraph -ClientSecretCredential $Credentials -TenantId $TenantID -NoWelcome

# synchronize contacts for two users of two types (Member, Contact) using GUID prefix
Sync-O365PersonalContact -UserId '[email protected]', '[email protected]' -Verbose -MemberTypes 'Member', 'Contact' -GuidPrefix 'O365Synchronizer' -WhatIf -PassThru | Format-Table *

As you can see above, to synchronize all users/contacts to two users, all you have to do is run two commands:

  • Connect-MgGraph to authorize the tenant
  • Sync-O365PersonalContact and use the UserId parameter to provide the UPN of users you want to deliver with synchronization of Members/Contacts.

Once executed, you get

Now, if we change the command to exclude Members and only synchronize contacts, you will see that it starts removing members and leaving only contacts in place.

Notice that we have a WhatIf switch to quickly test it before running wild. Using the PassThru parameter allows you to take the output from that command and send it to email or build a report around it.

The following permissions are required to use this functionality:

  • User.Read.All – to read users
  • OrgContact.Read.All – to read contacts
  • Contacts.ReadWrite – to write contacts

💡 Synchronizing users as contacts between tenants using Sync-O365Contact

The second functionality is doable using the Sync-O365Contact command. It's a bit different in what it does, as it uses the ExchangeOnlineManagement PowerShell module to synchronize contacts between tenants. The process is a bit different because we need to contact Tenant A using Microsoft Graph API but then synchronize those users/objects as contacts to Tenant B.

Currently, the Source objects to synchronize are objects provided by Get-MgUser. Still, providing functionality to synchronize objects from Active Directory, Exchange, or even external systems should be doable if there's a need for that.

# Source tenant (read only)
$ClientID = '9e1b3c36'
$TenantID = 'ceb371f6'
$ClientSecret = 'NDE8Q'

$Credentials = [pscredential]::new($ClientID, (ConvertTo-SecureString $ClientSecret -AsPlainText -Force))
Connect-MgGraph -ClientSecretCredential $Credentials -TenantId $TenantID -NoWelcome

$UsersToSync = Get-MgUser | Select-Object -First 5

# Destination tenant (writeable)
$ClientID = 'edc4302e'
Connect-ExchangeOnline -AppId $ClientID -CertificateThumbprint '2EC710' -Organization 'xxxxx.onmicrosoft.com'
Sync-O365Contact -SourceObjects $UsersToSync -Domains 'evotec.pl', 'gmail.com' -Verbose -WhatIf -LogPath 'C:\Temp\Logs\O365Synchronizer.log' -LogMaximum 5

This command works a bit differently when synchronizing users. You provide users to synchronize but also state from which domain those users are. Once the power starts running, it expects to control users from these specific fields. If users created in your target tenant are not on the list provided, those contacts will be deleted. If they exist in source, they will get updated. Essentially the command assumes complete control in adding, removing or updating contacts for given domains.

It's essential to reiterate! Those contacts will be removed if you have contacts in the target tenant that are not on the source lists for given domains. For the sake of exercise, if I tell it to synchronize the first 15 users but skip the first 5, the output will show that we are adding some new users, but at the same time, we try to remove those that already exist.

$UsersToSync = Get-MgUser | Select-Object -First 15 -Skip 5

Of course, I'm showing one-way sync, but nothing stops you from reverting commands, getting users from the target tenant, and pushing them to the source tenant. I would expect, however, that this would be done in another script by the admin of the second tenant, but in theory, you could just run it in a single PowerShell script.

The following permissions are required on the source tenant to use this functionality:

  • User.Read.All – to read users
On target tenant, you should use:
  • Exchange.ManageAsApp – to read/write contacts in Exchange (remember to add application to Exchange Recipient Administrator role)

💡 Installing the module

Installing O365Synchronizer (or updating) is as easy as executing a single command

Install-Module O365Synchronizer -Force -Verbose

To review sources or build your version of my module, you can find the project on the O365Synchronize GitHub page.

The post Syncing Global Address List (GAL) to personal contacts and between Office 365 tenants with PowerShell appeared first on Evotec.

]]>
Active Directory Health Check using Microsoft Entra Connect Health Service https://evotec.pl/active-directory-health-check-using-microsoft-entra-connect-health-service/ Sun, 08 Oct 2023 14:36:57 +0000 https://evotec.xyz/?p=18421 Active Directory (AD) is crucial in managing identities and resources within an organization. Ensuring its health is pivotal for the seamless operation of various services. Today, I decided to look at Microsoft Entra Connect Health (Azure AD Connect Health) service, which allows monitoring Azure AD Connect, ADFS, and Active Directory. This means that under a single umbrella, you can have an overview of three services health. But is it worth it?

The post Active Directory Health Check using Microsoft Entra Connect Health Service appeared first on Evotec.

]]>

Active Directory (AD) is crucial in managing identities and resources within an organization. Ensuring its health is pivotal for the seamless operation of various services. Today, I decided to look at Microsoft Entra Connect Health (Azure AD Connect Health) service, which allows monitoring Azure AD Connect, ADFS, and Active Directory. This means that under a single umbrella, you can have an overview of three services health. But is it worth it?

Before we check what this service has to offer, after reading the documentation it's clear Microsoft needs to do some updates, as depending on where you look you will find discrepancies in what the service is for. In the installation document Install the Microsoft Entra Connect Health agents there are references that this service is actually for Microsoft Entra DS, which is the new name for Azure Active Directory DS, where you explicitly can't install any agents as it's Microsoft-hosted.

For example, to get data from your Active Directory Federation Services (AD FS) infrastructure, you must install the agent on the AD FS server and on the Web Application Proxy server. Similarly, to get data from your on-premises Microsoft Entra Domain Services (Microsoft Entra DS) infrastructure, you must install the agent on the domain controllers.

Different docs, such as Using Microsoft Entra Connect Health with AD DS points to support Windows Server 2008 R2, Windows Server 2012, and Windows Server 2016, skipping new versions of servers. On the other hand, the FAQ for Microsoft Entra Connect Health correctly refers to more systems being supported and to Active Directory Domain Services (AD DS). Whether this is just a simple case of going overboard with renaming everything with Active Directory DS in its name to Microsoft Entra DS, or we are looking for a future rename of Active Directory, we will have to wait and see. In the meantime, let's test the Entra Connect Health agent for AD DS and see how it works. The FAQ document also points to different systems being supported (Windows 2008 R2 and Windows 21012 aren't up-to-date anymore), and I would trust it a bit more than the first mentioned document.

💡 Deploying Microsoft Entra Health Agent

Deployment of agents is pretty straightforward and follows the standard process:

  • Download the agent (MSI file)
  • Copy it to the Domain Controller
  • Run it, and let it ask you for Entra ID (Azure AD) credentials. You will require Global Admin to register a health agent.

That's it; now wait for the data to show in the Dashboard. Of course, if you have plenty of Domain Controllers, Microsoft provides the ability to install it using PowerShell.

💡 Microsoft Entra Health Dashboard for Active Directory (AD DS)

The dashboard is pretty minimalistic showing essential information about the domain, replication status, alerts, and basic charts for LDAP, NTLM, and Kerberos authentications.

As you can see on the screenshots above, it also gives you a quick overview of the number of DCs monitored (out of all available) and domains within the forest and sites it sees. According to it I have 2 out of 5 DCs, two domains, and one site.

You can also deep-dive into Performance Monitors Collection where you can select multiple other counters

  • ATQ estimated queue delay
  • ATQ outstanding queued requests
  • ATQ request latency
  • ATQ threads LDAP
  • ATQ threads other
  • ATQ threads total
  • Directory replication agent inbound bytes total per second
  • Directory replication agent outbound bytes total per second
  • Domain services threads in use
  • Free disk space
  • Kerberos authentications
  • LDAP active threads
  • LDAP bind time
  • LDAP searches per second
  • LDAP successful binds per second
  • NTLM authentications per second
  • Replication queue
  • TCP connections established
  • Used memory percentage
  • Used processor percentage

For each chart, you can drill down, see details, and filter out data per Domain Controller

💡 Mix & Match with other Azure Elements

Every chart can also be sent to a global dashboard to create a mix & match with other Azure Services. Unfortunately, after I pinned a few charts to my private dashboard and came back a bit later to it –  I was greeted with not-found messages, making this functionality more like a gimmick.

Maybe spending enough time fighting the dashboard, saving it multiple times, and fixing the positioning will be helpful. After playing with the dashboard for a few minutes where the data would be gone, or I had to go back to Health Service to add more charts, my patience ran out.

💡 Domain Controllers overview

Every element is clickable to drill down one or two levels. We can see which Domain Controllers are monitored, their status, and what roles on which DC are available. As you can see below, 2 out of 5 domain controllers are installed, and monitoring is available for them.

After I left the newly implemented solution running for a whole night, in the morning, it suggested I only have one domain, one site, and two out of two DCs in the forest. I tried refreshing, clicking icons, and going in or out, and the data suggested that my forest suddenly is much smaller than it is.

After installing another agent and waiting 5 minutes, it was able to pick up that I have another domain and and another DC.

But it still is missing two other DCs, so it left me a bit worried about deployment to production. As part of the same view, you can choose additional columns that show you critical information that is very useful for Domain Admins

  • LAST UPLOADED
  • LAST BOOT TIME
  • PDC REACHABLE
  • GC REACHABLE
  • SYSVOL STATE
  • DC ADVERTISED
  • OS NAME

With a simple glance, you can see potential issues in your forest (as long as you condition that this isn't live view but monitoring with a delay).

As you can see above, the Last Uploaded column gave different times: 9:00, 9:05, and 8:55, and when I took this screenshot, it was already 9:20. After waiting a bit more, it seems the actual refresh time is every 30 minutes. I am also not sure if this can be adjusted. More up-to-date information would be beneficial, but maybe a bit too performance-intensive. According to FAQ, the actual impact on the given server is:

  • CPU consumption: ~1-5% increase.
  • Memory consumption: Up to 10 % of the total system memory.

The agent is also not supported on Server Core, which, if you have invested heavily for a more secure version of Windows, you're a bit out of luck on this one.

💡 Detecting replication failures and reviewing them

To test if the service works, I shutdown one of the Domain Controllers, and the service did pick it up and let me know that replication is not working correctly.

You can also go into Replication Status and see what replication is failing.

When any health problems are detected, an email is sent, and alerts are generated in the console, giving you a quick overview of its status.

The alerts can't be suppressed or marked as fixed. They are generated automatically on error; if you set them on DC, the service will resolve them automatically. In the dashboard, you can see the history of alerts for the last 6 hours, the past 24 hours, or the past seven days. This is enough to cover day-to-day monitoring, but you can't see a more extended period to see any patterns of problems reoccurring over an extended period.

By default, only Global Administrators get email notifications about issues with Active Directory, but it can be changed to notify any other email addresses.

Once the issue is detected, the email is sent „as soon as possible.” Since I have completely shut down DC, two errors have been detected. One was a replication issue; the other was LDAP ping.

💡 Support for Multiple Active Directory Forests

Azure Active Directory Connect Health supports multiple forests so it's possible to register all your domains

💡 Licensing for Entra Connect Health Agent

While it may seem it's a free service, it requires Azure AD Premium P1 or P2 licenses. For every domain controller you want to monitor, you need 25 Azure AD Premium licenses assigned to your tenant, except for Azure AD Connect Server, which requires only one approval.

  • If you have 1 and 1 Azure AD Connect, you will need 51 licenses.
  • If you have 10 and 1 Azure AD Connect, you will need 251 licenses.
  • If you have 100 controllers and 2 Azure AD Connect, you will need 2502 licenses.

Most companies who are invested in Office 365 usually have P1 and P2 licenses already, as it brings other benefits to the table, so for them, it's primarily free addition.

💡 Pros & Cons for Microsoft Entra Connect Health

Microsoft Entra Health Service for ADDS has its pros and cons:

  • Supports monitoring multiple Active Directory forests
  • Agents communicate every 30 minutes. For 30 minutes, the service doesn't even notice any issues.
  • I've shut down one Domain Controller (ADRODC) with no other DC being monitored in the same domain, which would potentially detect problems with replications. It took 4 hours to get a notification that the agent was not responding. For 2 hours, the server was reporting in the console as perfectly fine, and only after 4 hours did the notification come in. The only thing that gave it away that something was wrong was the Last Upload column was not updated.
  • For another server, shutting it down triggered a replication error in the console on another server, but the server that was down again was reported as working as expected. It would take 4 hours to get a notification about it being down totally.
  • The console started showing the first replication errors about 35 minutes after DC was shut, but it took over 80 minutes to get an email.
  • In one case, it took over 2 hours to auto-resolve alerts for broken replication even though Testimo testing showed the replication works fine.
  • There were occasional issues with GUI where the service would report that my Forest has only one domain and two domain controllers, and all were onboarded, where I had 2 out of 5 available. This issue fixed itself several hours later, correctly showing several domains and domain controllers.
  • Some errors will auto-resolve themselves only after 72 hours from the alert generation if the same error condition doesn't happen again within that timeframe.
  • The metrics provided in the console are helpful and can bring benefits if one doesn't have other systems that offer such monitoring.
  • The agents upgrade themselves automatically, meaning there is very little maintenance. It either works or it doesn't.
  • The agents don't require reboots during installation as long as NET 4.6.2 Framework is installed.

Overall, I believe the health service has potential and is beneficial for monitoring if you already have enough Azure AD Premium P1 or P2 licenses. Unfortunately, this service is not super helpful if one expects real-time monitoring.

💡 List of notifications supported by Microsoft Entra Connect service for ADDS

Here's a list of currently supported notifications/detections. Even without having real-time monitoring for large environments, detecting problems like this is super beneficial. Unless you have specialized software already able to deliver such detection with real-time monitoring, Microsoft Entra Health service for ADDS is worth investing in.

Alert Name Description
Domain controller is unreachable via LDAP ping Domain Controller isn't reachable via LDAP Ping. This can be caused due to Network issues or machine issues. As a result, LDAP Pings will fail.
Active Directory replication error encountered This domain controller is experiencing replication issues, which can be found by going to the Replication Status Dashboard. Replication errors may be due to improper configuration or other related issues. Untreated replication errors can lead to data inconsistency.
Domain controller is unable to find a PDC A PDC isn't reachable through this domain controller. This will lead to impacted user logons, unapplied group policy changes, and system time synchronization failure.
Domain controller is unable to find a Global Catalog server A global catalog server isn't reachable from this domain controller. It will result in failed authentications attempted through this Domain Controller.
Domain controller unable to reach local sysvol share Sysvol contains important elements from Group Policy Objects and scripts to be distributed within DCs of a domain. The DC won't advertise itself as DC and Group Policies won't be applied.
Domain Controller time is out of sync The time on this Domain Controller is outside of the normal Time Skew range. As a result, Kerberos authentications will fail.
Domain controller isn't advertising This domain controller isn't properly advertising the roles it's capable of performing. This can be caused by problems with replication, DNS misconfiguration, critical services not running, or because of the server not being fully initialized. As a result, domain controllers, domain members, and other devices won't be able to locate this domain controller. Additionally, other domain controllers might not be able to replicate from this domain controller.
GPSVC service isn't running If the service is stopped or disabled, settings configured by the admin won't be applied and applications and components won't be manageable through Group Policy. Any components or applications that depend on the Group Policy component might not be functional if the service is disabled.
DFSR and/or NTFRS services aren't running If both DFSR and NTFRS services are stopped, Domain Controllers won't be able to replicate sysvol data. sysvol Data will be out of consistency.
Netlogon service isn't running Logon requests, registration, authentication, and locating of domain controllers will be unavailable on this DC.
W32Time service isn't running If Windows Time Service is stopped, date and time synchronization will be unavailable. If this service is disabled, any services that explicitly depend on it will fail to start.
ADWS service isn't running If Active Directory Web Services service is stopped or disabled, client applications, such as Active Directory PowerShell, won't be able to access or manage any directory service instances that are running locally on this server.
Root PDC isn't Syncing from NTP Server If you do not configure the PDC to synchronize time from an external or internal time source, the PDC emulator uses its internal clock and is itself the reliable time source for the forest. If time isn't accurate on the PDC itself, all computers will have incorrect time settings.
Domain controller is quarantined This Domain Controller isn't connected to any of the other working Domain Controllers. This may be caused due to improper configuration. As a result, this DC isn't being used and won't replicate from/to anyone.
Outbound Replication is Disabled DCs with disabled Outbound Replication, won't be able to distribute any changes originating within itself.
Inbound Replication is Disabled DCs with disabled Inbound Replication, won't have the latest information. This condition can lead to logon failures.
LanmanServer service isn't running If this service is disabled, any services that explicitly depend on it will fail to start.
Kerberos Key Distribution Center service isn't running If KDC Service is stopped, users won't be able to authentication through this DC using the Kerberos v5 authentication protocol.
DNS service isn't running If DNS Service is stopped, computers and users using that server for DNS purposes will fail to find resources.
DC had USN Rollback When USN rollbacks occur, modifications to objects and attributes aren't inbound replicated by destination domain controllers that have previously seen the USN. Because these destination domain controllers believe they are up to date, no replication errors are reported in Directory Service event logs or by monitoring and diagnostic tools. USN rollback may affect the replication of any object or attribute in any partition. The most frequently observed side effect is that user accounts and computer accounts that are created on the rollback domain controller do not exist on one or more replication partners. Or, the password updates that originated on the rollback domain controller do not exist on replication partners.

💡 Monitoring Forest Replication with Testimo for ad-hoc verifications

To finalize this blog post, here's an output from Testimo, which I use to verify Forest Replication to confirm whether it was being reported by the Health Service. Contrary to the Health Service, it asks every DC in the forest to deliver information about replication health. This means it can take a while to see results across large environments.

Invoke-Testimo -Sources ForestReplication

The post Active Directory Health Check using Microsoft Entra Connect Health Service appeared first on Evotec.

]]>
Seamless HTML Report Creation: Harness the Power of Markdown with PSWriteHTML PowerShell Module https://evotec.pl/unlocking-seamless-html-report-creation-harness-the-power-of-markdown-with-pswritehtml-powershell-module/ Sun, 03 Sep 2023 16:59:27 +0000 https://evotec.xyz/?p=18357 In today's digital age, the ability to create compelling and informative HTML reports and documents is a crucial skill for professionals in various fields. Whether you're a data analyst, a system administrator, a developer, or simply someone who wants to present information in an organized and visually appealing manner, having the right tools at your disposal can make all the difference. That's where the PSWriteHTML PowerShell module steps in, offering an array of possibilities to suit your reporting needs.

The post Seamless HTML Report Creation: Harness the Power of Markdown with PSWriteHTML PowerShell Module appeared first on Evotec.

]]>

In today's digital age, the ability to create compelling and informative HTML reports and documents is a crucial skill for professionals in various fields. Whether you're a data analyst, a system administrator, a developer, or simply someone who wants to present information in an organized and visually appealing manner, having the right tools at your disposal can make all the difference. That's where the PSWriteHTML PowerShell module steps in, offering an array of possibilities to suit your reporting needs.

In this blog post, we'll explore the fascinating world of HTML report generation using PSWriteHTML, a versatile and powerful tool in the PowerShell arsenal. What sets PSWriteHTML apart is its flexibility. You can create HTML reports using standard commands or leverage the simplicity and readability of Markdown – the choice is yours. This coexistence of options ensures you can adapt your reporting workflow to your specific requirements.

Join us on this journey as we unlock the potential of the PSWriteHTML PowerShell module, showcasing how you can effortlessly create professional HTML reports, whether you prefer traditional commands or the elegance of Markdown. So, if you're ready to elevate your reporting game and discover a powerful, user-friendly way to craft HTML reports tailored to your needs, let's dive right in!

💡 Harnessing the Power of Markdown with PSWriteHTML: Three Distinct Approaches

In exploring the PSWriteHTML PowerShell module, we've uncovered a versatile tool that empowers us to effortlessly create stunning HTML reports and documents. One of its standout features is the New-HTMLMarkdown command, which allows us to integrate Markdown content into our reports seamlessly. What sets it apart is its flexibility, offering not just one but three distinct ways to incorporate Markdown into your HTML creations. Let's dive into each of these approaches.

💡 Using Direct Markdown as a Scriptblock

he first approach allows you to include Markdown directly within a scriptblock. This method provides fine-grained control over your Markdown content, enabling you to craft your report precisely as you envision it. Here's how it works:

New-HTMLMarkdown {
    '# Hello, Markdown!'
    'This is a sample paragraph.'
    '## Subheading'
    'More Markdown content here.'
}

In this example, the Markdown content is defined within the scriptblock, making it easy to structure and format your report. You can include headings, paragraphs, lists, and more, all while taking advantage of PSWriteHTML's features like table of contents generation (-EnableTOC).

💡 Loading Markdown Content from a File

The second approach simplifies the process of incorporating Markdown by allowing you to load content directly from a Markdown file. This is particularly useful when you have pre-existing Markdown documents that you want to include in your HTML report. Here's how you can achieve this:

New-HTMLMarkdown -FilePath "C:\Path\To\Your\File.md"

By specifying the path to your Markdown file, PSWriteHTML seamlessly incorporates its content into your HTML report. You can use this method to include documentation, README files, or any Markdown content you have readily available.

💡 Using an Array of Strings

The third approach provides a flexible way to include Markdown content as an array of strings. This approach is ideal for scenarios where you want to construct your Markdown content dynamically within your script. Here's how it looks:

New-HTMLMarkdown -Content '# Hello, Markdown!', '## Subheading', 'This is a test'

With this method, you can assemble your Markdown content programmatically, offering great flexibility in customizing your report's content. These three distinct approaches allow you to choose the most suitable method for your reporting needs. Whether you prefer to work directly with Markdown in a ScriptBlock, load content from a file, or construct content dynamically as an array of strings, PSWriteHTML has you covered.

💡 Beyond Markdown: Crafting Comprehensive HTML Documents with PSWriteHTML

While the New-HTMLMarkdown command within the PSWriteHTML PowerShell module shines as a powerful tool for incorporating Markdown content into your reports; its capabilities extend beyond simple text. PSWriteHTML allows you to create rich and comprehensive HTML documents by combining Markdown with a wide array of other commands.

Imagine weaving together Markdown-based documentation with interactive elements like calendars, charts, and tables, all within the same HTML document. This seamless integration is where PSWriteHTML truly excels, making it a valuable asset for anyone who needs to present diverse and engaging information. Let's explore how this can be achieved.

New-HTML {
    New-HTMLTabStyle -BorderRadius 0px -TextTransform capitalize -BackgroundColorActive SlateGrey
    New-HTMLSectionStyle -BorderRadius 0px -HeaderBackGroundColor Grey -RemoveShadow
    New-HTMLPanelStyle -BorderRadius 0px
    New-HTMLTableOption -DataStore JavaScript -BoolAsString -ArrayJoinString ', ' -ArrayJoin
    New-HTMLSection {
        # as an array of strings
        New-HTMLMarkdown -Content '# Hello, Markdown!', '## Hello, Markdown!', 'Ok this is a test', '### Hello, Markdown!'
    }
    New-HTMLSection {
        # as a scriptblock
        New-HTMLMarkdown {
            '# Testing Header 1'
            'This is TOC'
            '[TOC]'
            '## Testing Header 2'
            'Ok this is a test'
            '## Testing Header 3'
            'Ok this is a test'
            '## Testing Header 4'
            'Ok this is a test'
            '### Testing Header 5'
        }
    }
    New-HTMLSection -Invisible {
        # as a file
        New-HTMLSection {
            New-HTMLMarkdown -FilePath "$PSScriptRoot\..\..\readme.md"
        }
        New-HTMLSection {
            New-HTMLMarkdown -FilePath "C:\Support\GitHub\ADEssentials\readme.md" -SanitezeHTML
        }

        New-HTMLSection {
            New-HTMLMarkdown -FilePath "C:\Support\GitHub\PSTeams\readme.md" -EnableOpenLinksInNewWindow
        }
        New-HTMLSection {
            New-HTMLMarkdown -FilePath "C:\Support\GitHub\PowerFederatedDirectory\README.MD"
        }
    }
} -ShowHTML:$true -Online -FilePath $PSScriptRoot\Example-Markdown.html

As you see above, I've used all three mentioned methods. I've used markdown by hand, I've loaded four different files, and I have also used them as ScriptBlock.

💡 Markdown as Part of a Larger Canvas

When you work with PSWriteHTML, you don't have to use New-HTMLMarkdown it in isolation. Instead, you can embed Markdown content within a broader canvas created with various other PSWriteHTML commands. For instance, you can:

  • Use New-HTMLCalendar to incorporate interactive calendars that display essential dates and events.
  • Leverage New-HTMLChart to visualize data with interactive charts and graphs.
  • Employ New-HTMLTable to present structured data in a tabular format.
  • Add navigation menus, headers, and footers to enhance the document's usability and aesthetics.

Here's a glimpse of what this combination can look like:

$ProcessSmaller = Get-Process | Select-Object -First 5

New-HTML {
    New-HTMLTabStyle -BorderRadius 0px -TextTransform capitalize -BackgroundColorActive SlateGrey
    New-HTMLSectionStyle -BorderRadius 0px -HeaderBackGroundColor Grey -RemoveShadow
    New-HTMLPanelStyle -BorderRadius 0px
    New-HTMLTableOption -DataStore JavaScript -BoolAsString -ArrayJoinString ', ' -ArrayJoin

    New-HTMLHeader {
        New-HTMLSection -Invisible {
            New-HTMLPanel -Invisible {
                New-HTMLImage -Source 'https://evotec.pl/wp-content/uploads/2015/05/Logo-evotec-012.png' -UrlLink 'https://evotec.pl/' -AlternativeText 'My other text' -Class 'otehr' -Width '50%'
            }
            New-HTMLPanel -Invisible {
                New-HTMLImage -Source 'https://evotec.pl/wp-content/uploads/2015/05/Logo-evotec-012.png' -UrlLink 'https://evotec.pl/' -AlternativeText 'My other text' -Width '20%'
            } -AlignContentText right
        }
    }
    New-HTMLSection {
        New-HTMLSection -HeaderText 'Test 1' {
            New-HTMLTable -DataTable $ProcessSmaller
        }
        New-HTMLSection -HeaderText 'Test 2' {
            New-HTMLCalendar {
                New-CalendarEvent -Title 'Active Directory Meeting' -Description 'We will talk about stuff' -StartDate (Get-Date)
                New-CalendarEvent -Title 'Lunch' -StartDate (Get-Date).AddDays(2).AddHours(-3) -EndDate (Get-Date).AddDays(3) -Description 'Very long lunch'
            }
        }
    }
    New-HTMLSection -Invisible {
        New-HTMLTabPanel {
            New-HTMLTab -Name 'PSWriteHTML from File' {
                # as a file
                New-HTMLSection {
                    New-HTMLMarkdown -FilePath "$PSScriptRoot\..\..\readme.md"
                }
            }
            New-HTMLTab -Name 'ADEssentials from File' {
                New-HTMLSection {
                    New-HTMLMarkdown -FilePath "C:\Support\GitHub\ADEssentials\readme.md"
                }
            }
        } -Theme elite
    }

    New-HTMLFooter {
        New-HTMLSection -Invisible {
            New-HTMLPanel -Invisible {
                New-HTMLImage -Source 'https://evotec.pl/wp-content/uploads/2015/05/Logo-evotec-012.png' -UrlLink 'https://evotec.pl/' -AlternativeText 'My other text' -Class 'otehr' -Width '50%'
            }
            New-HTMLPanel -Invisible {
                New-HTMLImage -Source 'https://evotec.pl/wp-content/uploads/2015/05/Logo-evotec-012.png' -UrlLink 'https://evotec.pl/' -AlternativeText 'My other text' -Width '20%'
            } -AlignContentText right
        }
    }
} -ShowHTML:$true -Online -FilePath $PSScriptRoot\Example-Markdown1.html

This example seamlessly blends Markdown sections with interactive elements such as calendars and tables. This approach allows you to create comprehensive and visually appealing reports that cater to various aspects of your project or presentation.

💡 Tailoring Your Reports to Perfection

Mixing and matchingMarkdown with other PSWriteHTML commands give you the freedom to tailor your reports to perfection. Whether you're delivering a project update, sharing research findings, or creating interactive documentation, PSWriteHTML empowers you to tell your story compellingly and informally.

If you don't know PSWriteHTML, please read those articles below to understand how you can use its power to fulfill your goals. All the topics described above are just a small part of what PSWriteHTML can do.

To get yourself up to speed with PSWriteHTML, all you have to do is install the module directly from PowerShellGallery.

Install-Module PSWriteHTML -Force -Verbose

I hope you enjoyed this blog post and the new PSWriteHTML feature.

The post Seamless HTML Report Creation: Harness the Power of Markdown with PSWriteHTML PowerShell Module appeared first on Evotec.

]]>