<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[DevOps4Dummies]]></title><description><![CDATA[DevOps4Dummies]]></description><link>https://devops4dummies.com</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1758281036656/b52b6d16-9bee-4150-a962-e437198dd83e.png</url><title>DevOps4Dummies</title><link>https://devops4dummies.com</link></image><generator>RSS for Node</generator><lastBuildDate>Fri, 01 May 2026 06:12:23 GMT</lastBuildDate><atom:link href="https://devops4dummies.com/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[CAdvisor Container UP/DOWN Status with Prometheus in Grafana]]></title><description><![CDATA[Why Traditional Docker Container Monitoring Fails
Most DevOps teams struggle with Docker container status monitoring using conventional approaches that rely on basic container metrics, such as CPU usage, memory consumption, and network traffic. Howev...]]></description><link>https://devops4dummies.com/cadvisor-container-updown-status-with-prometheus-in-grafana</link><guid isPermaLink="true">https://devops4dummies.com/cadvisor-container-updown-status-with-prometheus-in-grafana</guid><category><![CDATA[docker container]]></category><category><![CDATA[Grafana]]></category><category><![CDATA[#prometheus]]></category><category><![CDATA[monitoring]]></category><category><![CDATA[Status]]></category><category><![CDATA[cadvisor]]></category><category><![CDATA[observability]]></category><category><![CDATA[metrics]]></category><category><![CDATA[visualization]]></category><category><![CDATA[promql]]></category><category><![CDATA[Devops]]></category><dc:creator><![CDATA[Hardik Lokwani]]></dc:creator><pubDate>Tue, 23 Sep 2025 10:57:44 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1758624252576/7e0f5bd7-4b5e-456a-93c1-ed4e24058aee.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<hr />
<h2 id="heading-why-traditional-docker-container-monitoring-fails">Why Traditional Docker Container Monitoring Fails</h2>
<p>Most DevOps teams struggle with Docker container status monitoring using conventional approaches that rely on basic container metrics, such as CPU usage, memory consumption, and network traffic. However, these metrics fail to answer the fundamental question: "Is my container actually running right now?" Traditional container monitoring approaches suffer from critical limitations:</p>
<p><strong>Stale Metrics Problem</strong>: Prometheus continues to show metrics for stopped containers, creating false positives in Docker container monitoring dashboards and misleading operational teams.</p>
<p><strong>Metric Persistence Issues</strong>: When Docker containers stop, their metrics don't immediately disappear from Prometheus, causing confusion about actual container status and hampering effective container health monitoring.</p>
<p><strong>Complex Query Requirements</strong>: Existing Prometheus container monitoring solutions require complex, error-prone queries that are difficult to understand and maintain in production environments.</p>
<p><strong>Inconsistent Monitoring Behavior</strong>: Some container status queries work intermittently or fail during container restarts, network issues, or temporary outages, compromising reliable Docker monitoring.</p>
<h2 id="heading-the-ultimate-prometheus-query-for-docker-container-status">The Ultimate Prometheus Query for Docker Container Status</h2>
<p>After extensive testing in production environments, this Prometheus query delivers accurate Docker container status monitoring:</p>
<pre><code class="lang-markdown">count(time()-container<span class="hljs-emphasis">_last_</span>seen{name=~"<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">container_name</span>&gt;</span></span>"}<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">=60)</span> <span class="hljs-attr">or</span> <span class="hljs-attr">vector</span>(<span class="hljs-attr">0</span>)</span></span>
</code></pre>
<p>This Docker container status monitoring query elegantly solves container detection problems by leveraging the <code>container_last_seen</code> metric from cAdvisor combined with Prometheus time functions and vector operations for bulletproof UP/DOWN status detection.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758623067217/0f8c77d1-49b8-4235-9b7c-9adc41a2a025.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-how-the-container-status-detection-query-works">How the Container Status Detection Query Works</h2>
<p>Understanding each component of this Prometheus container monitoring query ensures successful implementation:</p>
<h3 id="heading-the-containerlastseen-metric-for-container-monitoring">The container_last_seen Metric for Container Monitoring</h3>
<p>The <code>container_last_seen</code> metric provided by cAdvisor (Container Advisor) represents the exact timestamp when Docker containers were last observed running. This metric is crucial for accurate container status monitoring because:</p>
<ul>
<li><p>Provides definitive timestamps of the container's last known activity</p>
</li>
<li><p>Automatically updates by cAdvisor while Docker containers are running</p>
</li>
<li><p>Disappears from metrics endpoints when containers stop, enabling precise detection logic</p>
</li>
</ul>
<h3 id="heading-time-based-container-health-detection-logic">Time-Based Container Health Detection Logic</h3>
<p>The expression <code>time()-container_last_seen{name=~"&lt;container_name&gt;"}</code> calculates seconds since the container was last active, forming the foundation of reliable Docker container monitoring:</p>
<ul>
<li><p><strong>Running Container</strong>: Returns small values (typically under scrape interval)</p>
</li>
<li><p><strong>Stopped Container</strong>: Returns large values or no data</p>
</li>
<li><p><strong>60-Second Threshold</strong>: Configurable tolerance for network delays and scraping intervals</p>
</li>
</ul>
<h3 id="heading-binary-status-output-with-count-function">Binary Status Output with Count Function</h3>
<p>The <code>count()</code> function transforms time calculations into a binary container status for a clear Grafana dashboard visualization:</p>
<ul>
<li><p><strong>Count Result = 1</strong>: Container is UP (last seen within threshold)</p>
</li>
<li><p><strong>Count Result = 0</strong>: Container is DOWN (not seen recently)</p>
</li>
<li><p><strong>Multiple Container Support</strong>: Handles container scaling automatically</p>
</li>
</ul>
<h3 id="heading-reliable-fallback-with-vector-function">Reliable Fallback with Vector Function</h3>
<p>The <code>or vector(0)</code> ensures consistent Docker container monitoring results:</p>
<ul>
<li><p><strong>No Matching Containers</strong>: Returns 0 instead of "No Data"</p>
</li>
<li><p><strong>Query Error Handling</strong>: Prevents Grafana dashboard panel failures</p>
</li>
<li><p><strong>Consistent Output</strong>: Always provides numeric results for value mappings</p>
</li>
</ul>
<h2 id="heading-step-by-step-grafana-implementation-guide">Step-by-Step Grafana Implementation Guide</h2>
<h3 id="heading-step-1-create-docker-container-status-panel">Step 1: Create Docker Container Status Panel</h3>
<p>Implement this Docker container status monitoring solution in Grafana:</p>
<ol>
<li><p><strong>Add New Panel</strong>: Create a Stat panel in your Grafana dashboard</p>
</li>
<li><p><strong>Configure Prometheus Data Source</strong>: Select your Prometheus data source</p>
</li>
<li><p><strong>Enter Container Monitoring Query</strong>:</p>
</li>
</ol>
<pre><code class="lang-markdown">count(time()-container<span class="hljs-emphasis">_last_</span>seen{name=~"your<span class="hljs-emphasis">_container_</span>name"}<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">=60)</span> <span class="hljs-attr">or</span> <span class="hljs-attr">vector</span>(<span class="hljs-attr">0</span>)</span></span>
</code></pre>
<p>Replace <code>"your_container_name"</code> with your actual Docker container name or regex patterns for multiple containers.</p>
<h3 id="heading-step-2-configure-value-mappings-for-updown-status">Step 2: Configure Value Mappings for UP/DOWN Status</h3>
<p>Essential Grafana configuration for visual container status indication makes this Docker container monitoring solution immediately actionable:</p>
<p><strong>Value Mapping Setup</strong>:</p>
<ul>
<li><p><strong>Value 1</strong> = Display "UP" with green background</p>
</li>
<li><p><strong>Value 0</strong> = Display "DOWN" with red background</p>
<p>  <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1758623191850/992ab95f-d733-448f-a5de-b6a7bdfa3433.png" alt class="image--center mx-auto" /></p>
</li>
</ul>
<h3 id="heading-step-3-optimize-visual-formatting">Step 3: Optimize Visual Formatting</h3>
<p>Configure these Grafana settings for optimal Docker container monitoring visualization:</p>
<ul>
<li><p>Set panel type to <strong>Stat</strong> for a clear status display</p>
</li>
<li><p>Enable <strong>Colored background</strong> for immediate status recognition</p>
</li>
<li><p>Adjust <strong>font size</strong> for dashboard visibility</p>
</li>
<li><p>Add custom styling for enhanced user experience</p>
</li>
</ul>
<h2 id="heading-advanced-docker-container-monitoring-use-cases">Advanced Docker Container Monitoring Use Cases</h2>
<h3 id="heading-multi-container-environment-monitoring">Multi-Container Environment Monitoring</h3>
<p>Monitor multiple Docker containers with pattern matching for comprehensive container status monitoring:</p>
<pre><code class="lang-markdown"><span class="hljs-section"># Monitor web service containers</span>
count(time()-container<span class="hljs-emphasis">_last_</span>seen{name=~"web.<span class="hljs-emphasis">*|api.*</span>|worker.<span class="hljs-emphasis">*"}<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">=60)</span> <span class="hljs-attr">or</span> <span class="hljs-attr">vector</span>(<span class="hljs-attr">0</span>)</span></span></span>
</code></pre>
<h3 id="heading-adjustable-container-monitoring-thresholds">Adjustable Container Monitoring Thresholds</h3>
<p>Customize monitoring sensitivity based on your Docker container monitoring requirements:</p>
<pre><code class="lang-markdown"><span class="hljs-section"># Strict monitoring (30 seconds)</span>
count(time()-container<span class="hljs-emphasis">_last_</span>seen{name=~"<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">container_name</span>&gt;</span></span>"}<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">=30)</span> <span class="hljs-attr">or</span> <span class="hljs-attr">vector</span>(<span class="hljs-attr">0</span>)

# <span class="hljs-attr">Relaxed</span> <span class="hljs-attr">monitoring</span> (<span class="hljs-attr">2</span> <span class="hljs-attr">minutes</span>)
<span class="hljs-attr">count</span>(<span class="hljs-attr">time</span>()<span class="hljs-attr">-container_last_seen</span>{<span class="hljs-attr">name</span>=<span class="hljs-string">~</span>"&lt;<span class="hljs-attr">container_name</span>&gt;</span></span>"}<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">=120)</span> <span class="hljs-attr">or</span> <span class="hljs-attr">vector</span>(<span class="hljs-attr">0</span>)</span></span>
</code></pre>
<h3 id="heading-service-group-container-monitoring">Service Group Container Monitoring</h3>
<p>Monitor Docker container availability by service groups for organized infrastructure monitoring:</p>
<pre><code class="lang-markdown">count(time()-container<span class="hljs-emphasis">_last_</span>seen{name=~"database-.<span class="hljs-emphasis">*"}<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">=60)</span> <span class="hljs-attr">or</span> <span class="hljs-attr">vector</span>(<span class="hljs-attr">0</span>)</span></span></span>
</code></pre>
<h2 id="heading-best-practices-for-docker-container-monitoring-implementation">Best Practices for Docker Container Monitoring Implementation</h2>
<h3 id="heading-essential-monitoring-infrastructure-setup">Essential Monitoring Infrastructure Setup</h3>
<p>Ensure your Docker container monitoring stack includes:</p>
<ul>
<li><p><strong>Prometheus</strong>: Metric collection and storage for container monitoring</p>
</li>
<li><p><strong>cAdvisor</strong>: Container metrics collection (provides container_last_seen)</p>
</li>
<li><p><strong>Grafana</strong>: Dashboard visualization and alerting for container status</p>
</li>
</ul>
<h3 id="heading-container-monitoring-query-optimization">Container Monitoring Query Optimization</h3>
<ul>
<li><p>Use specific container name patterns to optimize metric collection</p>
</li>
<li><p>Adjust time thresholds based on scrape intervals and monitoring requirements</p>
</li>
<li><p>Implement label filtering for environment-specific container monitoring</p>
</li>
</ul>
<h3 id="heading-grafana-dashboard-design-for-container-monitoring">Grafana Dashboard Design for Container Monitoring</h3>
<ul>
<li><p>Create dedicated container status dashboards for different environments</p>
</li>
<li><p>Implement alerting rules based on Docker container status changes</p>
</li>
<li><p>Maintain consistent color schemes across monitoring dashboards</p>
</li>
</ul>
<h2 id="heading-troubleshooting-common-container-monitoring-issues">Troubleshooting Common Container Monitoring Issues</h2>
<h3 id="heading-no-data-in-docker-container-monitoring">No Data in Docker Container Monitoring</h3>
<p>If your container status query returns no data:</p>
<ul>
<li><p>Verify cAdvisor is running and collecting container metrics</p>
</li>
<li><p>Check container names match your regex patterns</p>
</li>
<li><p>Ensure Prometheus successfully scrapes cAdvisor metrics</p>
</li>
</ul>
<h3 id="heading-inconsistent-container-status-results">Inconsistent Container Status Results</h3>
<p>For inconsistent Docker container monitoring behavior:</p>
<ul>
<li><p>Adjust time thresholds to account for scrape intervals</p>
</li>
<li><p>Verify network connectivity between monitoring components</p>
</li>
<li><p>Check resource constraints on monitoring infrastructure</p>
</li>
</ul>
<h3 id="heading-grafana-value-mapping-issues">Grafana Value Mapping Issues</h3>
<p>If colors aren't displaying correctly in your container monitoring dashboard:</p>
<ul>
<li><p>Ensure value mappings are configured for both 0 and 1 values</p>
</li>
<li><p>Verify correct visualization type selection</p>
</li>
<li><p>Check threshold settings don't conflict with value mappings</p>
</li>
</ul>
<h2 id="heading-frequently-asked-questions-faq">Frequently Asked Questions (FAQ)</h2>
<h3 id="heading-what-makes-this-prometheus-query-better-for-docker-container-monitoring">What makes this Prometheus query better for Docker container monitoring?</h3>
<p>This query uses the <code>container_last_seen</code> metric, specifically designed for status detection, eliminating false positives from stale metrics that plague other Docker container monitoring methods. The combination with <code>count()</code> and <code>vector(0)</code> ensures reliable binary output, perfect for UP/DOWN status visualization.</p>
<h3 id="heading-how-quickly-does-the-query-detect-stopped-docker-containers">How quickly does the query detect stopped Docker containers?</h3>
<p>The query detects stopped containers within one Prometheus scrape interval plus your configured threshold (default 60 seconds). For faster container status detection, reduce both the scrape interval and threshold value while considering system overhead.</p>
<h3 id="heading-can-this-query-monitor-multiple-docker-containers-simultaneously">Can this query monitor multiple Docker containers simultaneously?</h3>
<p>Yes, use regex patterns in the container name filter like <code>{name=~"web-.*|api-.*"}</code> to monitor multiple containers. Each matching container contributes to the count, providing the total running containers matching your pattern.</p>
<h3 id="heading-what-happens-if-cadvisor-stops-working-during-container-monitoring">What happens if cAdvisor stops working during container monitoring?</h3>
<p>If cAdvisor becomes unavailable, the query returns 0 due to the <code>vector(0)</code> fallback, indicating containers are DOWN. This fail-safe behavior ensures you're alerted to monitoring infrastructure issues rather than receiving false UP status.</p>
<h3 id="heading-how-do-i-adjust-docker-container-monitoring-sensitivity">How do I adjust Docker container monitoring sensitivity?</h3>
<p>Modify the time threshold in the query: use <code>&lt;=30</code> for 30-second sensitivity or <code>&lt;=120</code> for 2-minute tolerance. Choose values based on your scrape interval and tolerance for brief network interruptions.</p>
<h3 id="heading-does-this-work-with-docker-swarm-or-kubernetes-containers">Does this work with Docker Swarm or Kubernetes containers?</h3>
<p>Yes, this container monitoring approach works with Docker Swarm, Kubernetes, and standalone Docker containers as long as cAdvisor can collect the <code>container_last_seen</code> metric from your container runtime.</p>
<h2 id="heading-conclusion-reliable-docker-container-status-monitoring">Conclusion: Reliable Docker Container Status Monitoring</h2>
<p>This Prometheus query revolutionizes Docker container status monitoring by providing accurate, real-time UP/DOWN detection that eliminates common monitoring pitfalls. The combination of <code>container_last_seen</code> metrics, intelligent time comparison, and proper Grafana value mapping creates a robust container monitoring solution that DevOps teams can trust in production environments.</p>
<p>By implementing this Docker container monitoring approach, you'll gain immediate visibility into container health, reduce false alarms, and ensure rapid response to actual container failures. The query's simplicity makes it maintainable while its reliability makes it production-ready for critical infrastructure monitoring.</p>
<p><strong>Key Benefits of This Container Monitoring Solution</strong>:</p>
<ul>
<li><p>Accurate real-time Docker container status detection</p>
</li>
<li><p>Eliminates false positives from stale container metrics</p>
</li>
<li><p>Simple implementation with powerful monitoring capabilities</p>
</li>
<li><p>Reliable UP/DOWN visualization in Grafana dashboards</p>
</li>
<li><p>Production-tested solution for critical container monitoring</p>
</li>
</ul>
<p>Transform your Docker container monitoring today with this bulletproof Prometheus query and experience the difference that accurate container status detection makes in your DevOps monitoring infrastructure.</p>
]]></content:encoded></item><item><title><![CDATA[How to Use Ansible in Windows: Windows Scheduled Tasks]]></title><description><![CDATA[In this post of the Ansible 4 Windows series, we will learn how to create, remove, disable, and restart scheduled tasks on Windows servers using Ansible.
If you’ve worked with Windows before, you already know how useful scheduled tasks can be, they a...]]></description><link>https://devops4dummies.com/how-to-use-ansible-for-managing-windows-scheduled-tasks</link><guid isPermaLink="true">https://devops4dummies.com/how-to-use-ansible-for-managing-windows-scheduled-tasks</guid><category><![CDATA[ansible]]></category><category><![CDATA[Windows]]></category><category><![CDATA[Devops]]></category><category><![CDATA[automation]]></category><category><![CDATA[configuration management]]></category><category><![CDATA[sysadmin]]></category><category><![CDATA[Powershell]]></category><category><![CDATA[YAML]]></category><category><![CDATA[Infrastructure as code]]></category><category><![CDATA[scheduled task]]></category><dc:creator><![CDATA[Hardik Lokwani]]></dc:creator><pubDate>Thu, 04 Sep 2025 10:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1756986509940/09ec9394-67c6-47a0-84e9-3b0bf2e09b53.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In this post of the <em>Ansible 4 Windows</em> series, we will learn how to <strong>create, remove, disable, and restart scheduled tasks on Windows servers</strong> using Ansible.</p>
<p>If you’ve worked with Windows before, you already know how useful scheduled tasks can be, they allow you to automate actions such as running scripts, opening applications, or triggering maintenance jobs at specific times. Now, with Ansible, you can manage these tasks consistently across multiple servers, without having to log into each one manually.</p>
<p>By the end of this guide, you will know how to:</p>
<ul>
<li><p>Create a scheduled task</p>
</li>
<li><p>Remove a scheduled task</p>
</li>
<li><p>Disable a scheduled task (with PowerShell support)</p>
</li>
<li><p>Restart a scheduled task (with PowerShell support)</p>
</li>
</ul>
<p>Let’s walk through each of these step by step.</p>
<hr />
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>Before we begin, make sure you have the following:</p>
<ol>
<li><p>A working <strong>Ansible control node</strong> (Linux).</p>
</li>
<li><p>One or more <strong>Windows servers</strong> configured as managed hosts.</p>
</li>
<li><p>Ansible collections installed for Windows:</p>
<pre><code class="lang-bash"> ansible-galaxy collection install community.windows ansible.windows
</code></pre>
</li>
<li><p>Basic familiarity with running Ansible playbooks.</p>
</li>
</ol>
<p>For disabling and restarting tasks, we will also need two small <strong>PowerShell scripts</strong> placed inside the Windows server. You can either:</p>
<ul>
<li><p>Manually copy them to the server (e.g., <code>C:\Users\Ansible\Scripts\</code>), or</p>
</li>
<li><p>Use Ansible’s <a target="_blank" href="http://ansible.windows.win"><code>ansible.windows.win</code></a><code>_copy</code> module to copy them over from your control node.</p>
</li>
</ul>
<p>We’ll cover the scripts later in this post.</p>
<hr />
<h2 id="heading-1-creating-a-scheduled-task">1. Creating a Scheduled Task</h2>
<p>Here’s a simple playbook that creates a scheduled task. This task will run two commands:</p>
<ul>
<li><p>Show the computer’s hostname</p>
</li>
<li><p>Show the user running the task</p>
</li>
</ul>
<pre><code class="lang-yaml"><span class="hljs-meta">---</span>
<span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Create</span> <span class="hljs-string">a</span> <span class="hljs-string">scheduled</span> <span class="hljs-string">task</span>
  <span class="hljs-attr">hosts:</span> <span class="hljs-string">windows</span>
  <span class="hljs-attr">gather_facts:</span> <span class="hljs-literal">true</span>
  <span class="hljs-attr">tasks:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Create</span> <span class="hljs-string">&lt;YourTaskName&gt;</span> <span class="hljs-string">to</span> <span class="hljs-string">run</span> <span class="hljs-string">cmd</span> <span class="hljs-string">commands</span>
      <span class="hljs-attr">community.windows.win_scheduled_task:</span>
        <span class="hljs-attr">name:</span> <span class="hljs-string">&lt;YourTaskName&gt;</span>
        <span class="hljs-attr">description:</span> <span class="hljs-string">open</span> <span class="hljs-string">command</span> <span class="hljs-string">prompt</span>
        <span class="hljs-attr">actions:</span>
          <span class="hljs-bullet">-</span> <span class="hljs-attr">path:</span> <span class="hljs-string">cmd.exe</span>
            <span class="hljs-attr">arguments:</span> <span class="hljs-string">/c</span> <span class="hljs-string">hostname</span>
          <span class="hljs-bullet">-</span> <span class="hljs-attr">path:</span> <span class="hljs-string">cmd.exe</span>
            <span class="hljs-attr">arguments:</span> <span class="hljs-string">/c</span> <span class="hljs-string">whoami</span>
        <span class="hljs-attr">triggers:</span>
          <span class="hljs-bullet">-</span> <span class="hljs-attr">type:</span> <span class="hljs-string">daily</span>
            <span class="hljs-attr">start_boundary:</span> <span class="hljs-string">'2025-08-21T15:20:00'</span>
        <span class="hljs-attr">username:</span> <span class="hljs-string">&lt;YourAnsibleUser&gt;</span> <span class="hljs-comment"># You can also use "SYSTEM" as username here.</span>
        <span class="hljs-attr">state:</span> <span class="hljs-string">present</span>
        <span class="hljs-attr">enabled:</span> <span class="hljs-literal">true</span>
</code></pre>
<p><strong>What this does:</strong></p>
<ul>
<li><p>Creates a task with the name <code>&lt;YourTaskName&gt;</code></p>
</li>
<li><p>Runs two commands (<code>hostname</code> and <code>whoami</code>) daily at 3:20 PM.</p>
</li>
<li><p>Executes as the <code>&lt;YourAnsibleUser&gt;</code> OR <code>SYSTEM</code> user.</p>
</li>
</ul>
<hr />
<h2 id="heading-2-removing-a-scheduled-task">2. Removing a Scheduled Task</h2>
<p>If you no longer need the task, you can remove it with the following playbook:</p>
<pre><code class="lang-yaml"><span class="hljs-meta">---</span>
<span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Remove</span> <span class="hljs-string">a</span> <span class="hljs-string">scheduled</span> <span class="hljs-string">task</span>
  <span class="hljs-attr">hosts:</span> <span class="hljs-string">windows</span>
  <span class="hljs-attr">gather_facts:</span> <span class="hljs-literal">true</span>
  <span class="hljs-attr">tasks:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Remove</span> <span class="hljs-string">&lt;YourTaskNAme&gt;</span>
      <span class="hljs-attr">community.windows.win_scheduled_task:</span>
        <span class="hljs-attr">name:</span> <span class="hljs-string">&lt;YourTaskNAme&gt;</span>
        <span class="hljs-attr">state:</span> <span class="hljs-string">absent</span>
</code></pre>
<p>This deletes the scheduled task called <code>&lt;YourTaskNAme&gt;</code> from the Windows server.</p>
<hr />
<h2 id="heading-3-disabling-a-scheduled-task-powershell">3. Disabling a Scheduled Task (PowerShell)</h2>
<p>Sometimes you don’t want to delete a task but simply <strong>disable</strong> it. Ansible doesn’t directly provide a disable option, so we will use a PowerShell script to handle this.</p>
<p>First, create a script called <a target="_blank" href="http://Disable-Task.ps"><code>Disable-Task.ps</code></a><code>1</code> with the following content:</p>
<pre><code class="lang-powershell"><span class="hljs-keyword">Param</span>(
    [<span class="hljs-type">Parameter</span>(<span class="hljs-type">Mandatory</span>=<span class="hljs-variable">$true</span>)]
    [<span class="hljs-built_in">string</span>]<span class="hljs-variable">$TaskName</span>
)

<span class="hljs-keyword">try</span> {
    <span class="hljs-variable">$task</span> = <span class="hljs-built_in">Get-ScheduledTask</span> <span class="hljs-literal">-TaskName</span> <span class="hljs-variable">$TaskName</span> <span class="hljs-literal">-ErrorAction</span> Stop
    <span class="hljs-built_in">Disable-ScheduledTask</span> <span class="hljs-literal">-InputObject</span> <span class="hljs-variable">$task</span>
    <span class="hljs-built_in">Write-Host</span> <span class="hljs-string">"Task '<span class="hljs-variable">$TaskName</span>' has been disabled successfully."</span>
} 
<span class="hljs-keyword">catch</span> {
    <span class="hljs-built_in">Write-Error</span> <span class="hljs-string">"Failed to disable task '<span class="hljs-variable">$TaskName</span>'. Error: <span class="hljs-variable">$_</span>"</span>
}
</code></pre>
<p>Save this file under:<br /><code>C:\Users\Ansible\Scripts\</code><a target="_blank" href="http://Disable-Task.ps"><code>Disable-Task.ps</code></a><code>1</code></p>
<p>Now, use the playbook below to run the script from Ansible:</p>
<pre><code class="lang-yaml"><span class="hljs-meta">---</span>
<span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Disable</span> <span class="hljs-string">Windows</span> <span class="hljs-string">Scheduled</span> <span class="hljs-string">Task</span>
  <span class="hljs-attr">hosts:</span> <span class="hljs-string">windows</span>
  <span class="hljs-attr">gather_facts:</span> <span class="hljs-literal">true</span>
  <span class="hljs-attr">vars:</span>
    <span class="hljs-attr">task_name:</span> <span class="hljs-string">"&lt;YourTaskName&gt;"</span>
    <span class="hljs-attr">script_path:</span> <span class="hljs-string">"C:\\Users\\&lt;YourAnsibleUser&gt;\\Scripts\\Disable-Task.ps1"</span>

  <span class="hljs-attr">tasks:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Disable</span> <span class="hljs-string">the</span> <span class="hljs-string">scheduled</span> <span class="hljs-string">task</span>
      <span class="hljs-attr">ansible.windows.win_shell:</span> <span class="hljs-string">|</span>
        <span class="hljs-string">powershell.exe</span> <span class="hljs-string">-ExecutionPolicy</span> <span class="hljs-string">Bypass</span> <span class="hljs-string">-File</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ script_path }}</span>"</span> <span class="hljs-string">-TaskName</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ task_name }}</span>"</span>
</code></pre>
<p>This will disable the task named <code>&lt;YourTaskNAme&gt;</code>.</p>
<hr />
<h2 id="heading-4-restarting-a-scheduled-task-powershell">4. Restarting a Scheduled Task (PowerShell)</h2>
<p>To restart a scheduled task, we will use another PowerShell script:</p>
<pre><code class="lang-powershell"><span class="hljs-keyword">Param</span>(
    [<span class="hljs-type">Parameter</span>(<span class="hljs-type">Mandatory</span>=<span class="hljs-variable">$true</span>)]
    [<span class="hljs-built_in">string</span>]<span class="hljs-variable">$TaskName</span>
)

<span class="hljs-keyword">try</span> {
    <span class="hljs-variable">$task</span> = <span class="hljs-built_in">Get-ScheduledTask</span> <span class="hljs-literal">-TaskName</span> <span class="hljs-variable">$TaskName</span> <span class="hljs-literal">-ErrorAction</span> Stop

    <span class="hljs-keyword">if</span> (<span class="hljs-variable">$task</span>.State <span class="hljs-operator">-eq</span> <span class="hljs-string">'Disabled'</span>) {
        <span class="hljs-built_in">Enable-ScheduledTask</span> <span class="hljs-literal">-InputObject</span> <span class="hljs-variable">$task</span>
        <span class="hljs-built_in">Write-Host</span> <span class="hljs-string">"Task '<span class="hljs-variable">$TaskName</span>' was disabled and has been enabled."</span>
    }

    <span class="hljs-variable">$taskState</span> = (<span class="hljs-built_in">Get-ScheduledTaskInfo</span> <span class="hljs-literal">-TaskName</span> <span class="hljs-variable">$TaskName</span>).State
    <span class="hljs-keyword">if</span> (<span class="hljs-variable">$taskState</span> <span class="hljs-operator">-eq</span> <span class="hljs-string">'Running'</span>) {
        <span class="hljs-built_in">Stop-ScheduledTask</span> <span class="hljs-literal">-InputObject</span> <span class="hljs-variable">$task</span>
        <span class="hljs-built_in">Write-Host</span> <span class="hljs-string">"Task '<span class="hljs-variable">$TaskName</span>' was running and has been stopped."</span>
    }

    <span class="hljs-built_in">Start-ScheduledTask</span> <span class="hljs-literal">-InputObject</span> <span class="hljs-variable">$task</span>
    <span class="hljs-built_in">Write-Host</span> <span class="hljs-string">"Task '<span class="hljs-variable">$TaskName</span>' has been started successfully."</span>
}
<span class="hljs-keyword">catch</span> {
    <span class="hljs-built_in">Write-Error</span> <span class="hljs-string">"Failed to restart task '<span class="hljs-variable">$TaskName</span>'. Error: <span class="hljs-variable">$_</span>"</span>
}
</code></pre>
<p>Save it as:<br /><code>C:\Users\Ansible\Scripts\</code><a target="_blank" href="http://Restart-Task.ps"><code>Restart-Task.ps</code></a><code>1</code></p>
<p>Here’s the Ansible playbook to call this script:</p>
<pre><code class="lang-yaml"><span class="hljs-meta">---</span>
<span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Restart</span> <span class="hljs-string">Windows</span> <span class="hljs-string">Scheduled</span> <span class="hljs-string">Task</span>
  <span class="hljs-attr">hosts:</span> <span class="hljs-string">windows</span>
  <span class="hljs-attr">gather_facts:</span> <span class="hljs-literal">true</span>
  <span class="hljs-attr">vars:</span>
    <span class="hljs-attr">task_name:</span> <span class="hljs-string">"&lt;YourTaskName&gt;"</span>
    <span class="hljs-attr">script_path:</span> <span class="hljs-string">"C:\\Users\\&lt;YourAnsibleUser&gt;\\Scripts\\Restart-Task.ps1"</span>

  <span class="hljs-attr">tasks:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Restart</span> <span class="hljs-string">the</span> <span class="hljs-string">scheduled</span> <span class="hljs-string">task</span>
      <span class="hljs-attr">ansible.windows.win_shell:</span> <span class="hljs-string">|</span>
        <span class="hljs-string">powershell.exe</span> <span class="hljs-string">-ExecutionPolicy</span> <span class="hljs-string">Bypass</span> <span class="hljs-string">-File</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ script_path }}</span>"</span> <span class="hljs-string">-TaskName</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ task_name }}</span>"</span>
</code></pre>
<hr />
<h2 id="heading-wrapping-up">Wrapping Up</h2>
<p>We have now covered how to:</p>
<ul>
<li><p><strong>Create</strong> a scheduled task using Ansible modules.</p>
</li>
<li><p><strong>Remove</strong> a scheduled task when no longer needed.</p>
</li>
<li><p><strong>Disable</strong> a task temporarily using PowerShell scripts.</p>
</li>
<li><p><strong>Restart</strong> a scheduled task and bring it back into action.</p>
</li>
</ul>
<p>Scheduled tasks are powerful for automating routine jobs on Windows servers. By combining them with Ansible, you gain the ability to manage these tasks consistently across multiple machines, saving time and reducing manual effort.</p>
]]></content:encoded></item><item><title><![CDATA[How to Use Ansible in Windows: Installing PostgreSQL]]></title><description><![CDATA[This article is part of the “Ansible for Windows” series. In this guide, we automate installation of PostgreSQL (Enterprise Edition) on a Windows host using Ansible. The playbook performs a silent and unattended install, starts the service, and clean...]]></description><link>https://devops4dummies.com/installing-enterprise-postgresql-using-ansible-on-windows</link><guid isPermaLink="true">https://devops4dummies.com/installing-enterprise-postgresql-using-ansible-on-windows</guid><category><![CDATA[ansible]]></category><category><![CDATA[PostgreSQL]]></category><category><![CDATA[automation]]></category><category><![CDATA[Windows]]></category><category><![CDATA[ansible-playbook]]></category><category><![CDATA[Devops]]></category><category><![CDATA[configuration]]></category><category><![CDATA[Installation]]></category><category><![CDATA[Developer]]></category><category><![CDATA[sysadmin]]></category><dc:creator><![CDATA[Hardik Lokwani]]></dc:creator><pubDate>Mon, 01 Sep 2025 22:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1756813925724/099dcf8b-5111-49df-9169-5d26a5ee5f97.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>This article is part of the <strong>“Ansible for Windows”</strong> series. In this guide, we automate installation of <strong>PostgreSQL (Enterprise Edition)</strong> on a Windows host using Ansible. The playbook performs a silent and unattended install, starts the service, and cleans up the installer file.</p>
<hr />
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>Before running the playbook:</p>
<ol>
<li><p><strong>Ansible controller node</strong><br /> You have Ansible installed on a Linux/macOS controller and can reach the Windows host.</p>
</li>
<li><p><strong>WinRM configured</strong><br /> The target Windows machine is accessible to Ansible over WinRM (HTTP or HTTPS), and your inventory/group vars are set appropriately (e.g., <code>ansible_connection=winrm</code>, <code>ansible_user</code>, <code>ansible_password</code>, <code>ansible_winrm_transport</code>).</p>
</li>
<li><p><strong>Installer available locally on the controller</strong><br /> Download the PostgreSQL Windows installer (<code>.exe</code>) <strong>to your Ansible controller</strong>.<br /> This workflow is ideal when the Windows machine has <strong>no internet access</strong>; Ansible will copy the installer to the host and run it there.</p>
</li>
<li><p><strong>Online alternative (optional)</strong><br /> If the Windows host <strong>does</strong> have internet access, you can install directly from a URL using <code>win_package</code>, skipping the copy step. An example is provided later in this article.</p>
</li>
</ol>
<hr />
<h2 id="heading-full-playbook-copy-paste-ready">Full Playbook (copy-paste ready)</h2>
<pre><code class="lang-yaml"><span class="hljs-meta">---</span>
<span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Install</span> <span class="hljs-string">PostgreSQL</span> <span class="hljs-string">silently</span> <span class="hljs-string">on</span> <span class="hljs-string">Windows</span>
  <span class="hljs-attr">hosts:</span> <span class="hljs-string">windows</span>
  <span class="hljs-attr">gather_facts:</span> <span class="hljs-literal">true</span>

  <span class="hljs-attr">vars:</span>
    <span class="hljs-comment"># Dummy paths &amp; credentials for illustration; replace for your environment.</span>
    <span class="hljs-attr">pg_installer_local_path:</span> <span class="hljs-string">"/dummy/path/postgresql-installer.exe"</span>
    <span class="hljs-attr">pg_installer_remote_path:</span> <span class="hljs-string">"C:\\Temp\\postgresql-installer.exe"</span>
    <span class="hljs-attr">pg_password:</span> <span class="hljs-string">"MyDummyPassword123"</span>
    <span class="hljs-attr">pg_port:</span> <span class="hljs-number">5433</span>
    <span class="hljs-attr">pg_major_version:</span> <span class="hljs-number">17</span>
    <span class="hljs-attr">pg_data_dir:</span> <span class="hljs-string">"C:\\PostgresData\\<span class="hljs-template-variable">{{ pg_major_version }}</span>\\data"</span>

  <span class="hljs-attr">tasks:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Gather</span> <span class="hljs-string">installed</span> <span class="hljs-string">PostgreSQL</span> <span class="hljs-string">info</span> <span class="hljs-string">(64-bit)</span>
      <span class="hljs-attr">ansible.windows.win_shell:</span> <span class="hljs-string">|
        Get-ChildItem "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall" |
        Get-ItemProperty |
        Where-Object { $_.DisplayName -like "PostgreSQL*" } |
        Select-Object DisplayName, DisplayVersion, PSChildName
</span>      <span class="hljs-attr">register:</span> <span class="hljs-string">pg_registry_info_64</span>

    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Filter</span> <span class="hljs-string">PostgreSQL</span> <span class="hljs-string">info</span>
      <span class="hljs-attr">ansible.builtin.set_fact:</span>
        <span class="hljs-attr">pg_installed_info:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ (pg_registry_info_64.stdout_lines) | unique }}</span>"</span>

    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Display</span> <span class="hljs-string">PostgreSQL</span> <span class="hljs-string">installed</span> <span class="hljs-string">info</span>
      <span class="hljs-attr">ansible.builtin.debug:</span>
        <span class="hljs-attr">msg:</span> <span class="hljs-string">"Installed PostgreSQL info: <span class="hljs-template-variable">{{ pg_installed_info }}</span>"</span>

    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Copy</span> <span class="hljs-string">PostgreSQL</span> <span class="hljs-string">installer</span> <span class="hljs-string">if</span> <span class="hljs-string">not</span> <span class="hljs-string">already</span> <span class="hljs-string">present</span>
      <span class="hljs-attr">ansible.windows.win_copy:</span>
        <span class="hljs-attr">src:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ pg_installer_local_path }}</span>"</span>
        <span class="hljs-attr">dest:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ pg_installer_remote_path }}</span>"</span>
      <span class="hljs-attr">args:</span>
        <span class="hljs-comment"># Kept to match the original approach; see "Idempotent copy alternative" below.</span>
        <span class="hljs-attr">creates:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ pg_installer_remote_path }}</span>"</span>

    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Install</span> <span class="hljs-string">PostgreSQL</span> <span class="hljs-string">silently</span>
      <span class="hljs-attr">ansible.windows.win_command:</span> <span class="hljs-string">&gt;
        "{{ pg_installer_remote_path }}"
        --mode unattended
        --unattendedmodeui none
        --superpassword "{{ pg_password }}"
        --servicename postgresql
        --serverport "{{ pg_port }}"
        --datadir "{{ pg_data_dir }}"
</span>      <span class="hljs-attr">args:</span>
        <span class="hljs-attr">creates:</span> <span class="hljs-string">"C:\\Program Files\\PostgreSQL\\<span class="hljs-template-variable">{{ pg_major_version }}</span>\\bin\\psql.exe"</span>

    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Ensure</span> <span class="hljs-string">PostgreSQL</span> <span class="hljs-string">service</span> <span class="hljs-string">is</span> <span class="hljs-string">running</span>
      <span class="hljs-attr">ansible.windows.win_service:</span>
        <span class="hljs-attr">name:</span> <span class="hljs-string">postgresql</span>
        <span class="hljs-attr">state:</span> <span class="hljs-string">started</span>
        <span class="hljs-attr">start_mode:</span> <span class="hljs-string">auto</span>

    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Remove</span> <span class="hljs-string">PostgreSQL</span> <span class="hljs-string">installer</span> <span class="hljs-string">if</span> <span class="hljs-string">still</span> <span class="hljs-string">present</span>
      <span class="hljs-attr">ansible.windows.win_file:</span>
        <span class="hljs-attr">path:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ pg_installer_remote_path }}</span>"</span>
        <span class="hljs-attr">state:</span> <span class="hljs-string">absent</span>
</code></pre>
<blockquote>
<p><strong>Important:</strong> The password and port are hard-coded here <strong>for demo only</strong>. For real environments, prefer <strong>Ansible Vault</strong>, a secrets manager, or a simple .env file.</p>
</blockquote>
<hr />
<h2 id="heading-step-by-step-explanation-with-code">Step-by-Step Explanation (with code)</h2>
<h3 id="heading-1-variables">1) Variables</h3>
<pre><code class="lang-yaml"><span class="hljs-attr">vars:</span>
  <span class="hljs-attr">pg_installer_local_path:</span> <span class="hljs-string">"/dummy/path/postgresql-installer.exe"</span>
  <span class="hljs-attr">pg_installer_remote_path:</span> <span class="hljs-string">"C:\\Temp\\postgresql-installer.exe"</span>
  <span class="hljs-attr">pg_password:</span> <span class="hljs-string">"MyDummyPassword123"</span>
  <span class="hljs-attr">pg_port:</span> <span class="hljs-number">5433</span>
  <span class="hljs-attr">pg_major_version:</span> <span class="hljs-number">17</span>
  <span class="hljs-attr">pg_data_dir:</span> <span class="hljs-string">"C:\\PostgresData\\<span class="hljs-template-variable">{{ pg_major_version }}</span>\\data"</span>
</code></pre>
<ul>
<li><p><code>pg_installer_local_path</code>: Where the <code>.exe</code> resides on the <strong>controller</strong>.</p>
</li>
<li><p><code>pg_installer_remote_path</code>: Where Ansible will place it on the <strong>Windows host</strong>.</p>
</li>
<li><p><code>pg_password</code>: PostgreSQL superuser password.</p>
</li>
<li><p><code>pg_port</code>: PostgreSQL server port (5432 is default; using 5433 here as an example).</p>
</li>
<li><p><code>pg_data_dir</code>: Data directory PostgreSQL will initialize.</p>
</li>
</ul>
<p>Replace the dummy paths/credentials with values for your environment.</p>
<hr />
<h3 id="heading-2-detect-existing-postgresql-windows-registry">2) Detect existing PostgreSQL (Windows Registry)</h3>
<pre><code class="lang-yaml"><span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Gather</span> <span class="hljs-string">installed</span> <span class="hljs-string">PostgreSQL</span> <span class="hljs-string">info</span> <span class="hljs-string">(64-bit)</span>
  <span class="hljs-attr">ansible.windows.win_shell:</span> <span class="hljs-string">|
    Get-ChildItem "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall" |
    Get-ItemProperty |
    Where-Object { $_.DisplayName -like "PostgreSQL*" } |
    Select-Object DisplayName, DisplayVersion, PSChildName
</span>  <span class="hljs-attr">register:</span> <span class="hljs-string">pg_registry_info_64</span>

<span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Filter</span> <span class="hljs-string">PostgreSQL</span> <span class="hljs-string">info</span>
  <span class="hljs-attr">ansible.builtin.set_fact:</span>
    <span class="hljs-attr">pg_installed_info:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ (pg_registry_info_64.stdout_lines) | unique }}</span>"</span>

<span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Display</span> <span class="hljs-string">PostgreSQL</span> <span class="hljs-string">installed</span> <span class="hljs-string">info</span>
  <span class="hljs-attr">ansible.builtin.debug:</span>
    <span class="hljs-attr">msg:</span> <span class="hljs-string">"Installed PostgreSQL info: <span class="hljs-template-variable">{{ pg_installed_info }}</span>"</span>
</code></pre>
<p>This gives visibility into existing installs and helps avoid redundant work.</p>
<hr />
<h3 id="heading-3-copy-the-installer-to-the-windows-host">3) Copy the installer to the Windows host</h3>
<pre><code class="lang-yaml"><span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Copy</span> <span class="hljs-string">PostgreSQL</span> <span class="hljs-string">installer</span> <span class="hljs-string">if</span> <span class="hljs-string">not</span> <span class="hljs-string">already</span> <span class="hljs-string">present</span>
  <span class="hljs-attr">ansible.windows.win_copy:</span>
    <span class="hljs-attr">src:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ pg_installer_local_path }}</span>"</span>
    <span class="hljs-attr">dest:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ pg_installer_remote_path }}</span>"</span>
  <span class="hljs-attr">args:</span>
    <span class="hljs-attr">creates:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ pg_installer_remote_path }}</span>"</span>
</code></pre>
<p>This transfers the <code>.exe</code> from the controller to the Windows machine—ideal for <strong>offline</strong> Windows hosts.</p>
<p><strong>Idempotent copy alternative (recommended):</strong></p>
<pre><code class="lang-yaml"><span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Check</span> <span class="hljs-string">if</span> <span class="hljs-string">installer</span> <span class="hljs-string">already</span> <span class="hljs-string">exists</span>
  <span class="hljs-attr">ansible.windows.win_stat:</span>
    <span class="hljs-attr">path:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ pg_installer_remote_path }}</span>"</span>
  <span class="hljs-attr">register:</span> <span class="hljs-string">installer_stat</span>

<span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Copy</span> <span class="hljs-string">PostgreSQL</span> <span class="hljs-string">installer</span>
  <span class="hljs-attr">ansible.windows.win_copy:</span>
    <span class="hljs-attr">src:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ pg_installer_local_path }}</span>"</span>
    <span class="hljs-attr">dest:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ pg_installer_remote_path }}</span>"</span>
  <span class="hljs-attr">when:</span> <span class="hljs-string">not</span> <span class="hljs-string">installer_stat.stat.exists</span>
</code></pre>
<hr />
<h3 id="heading-4-silent-unattended-installation">4) Silent, unattended installation</h3>
<pre><code class="lang-yaml"><span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Install</span> <span class="hljs-string">PostgreSQL</span> <span class="hljs-string">silently</span>
  <span class="hljs-attr">ansible.windows.win_command:</span> <span class="hljs-string">&gt;
    "{{ pg_installer_remote_path }}"
    --mode unattended
    --unattendedmodeui none
    --superpassword "{{ pg_password }}"
    --servicename postgresql
    --serverport "{{ pg_port }}"
    --datadir "{{ pg_data_dir }}"
</span>  <span class="hljs-attr">args:</span>
    <span class="hljs-attr">creates:</span> <span class="hljs-string">"C:\\Program Files\\PostgreSQL\\<span class="hljs-template-variable">{{ pg_major_version }}</span>\\bin\\psql.exe"</span>
</code></pre>
<ul>
<li><p>Runs the Enterprise PostgreSQL installer with no UI.</p>
</li>
<li><p><code>creates</code> makes the task idempotent by checking for <code>psql.exe</code> in the expected location.</p>
</li>
</ul>
<hr />
<h3 id="heading-5-ensure-the-windows-service-is-up-and-enabled">5) Ensure the Windows service is up and enabled</h3>
<pre><code class="lang-yaml"><span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Ensure</span> <span class="hljs-string">PostgreSQL</span> <span class="hljs-string">service</span> <span class="hljs-string">is</span> <span class="hljs-string">running</span>
  <span class="hljs-attr">ansible.windows.win_service:</span>
    <span class="hljs-attr">name:</span> <span class="hljs-string">postgresql</span>
    <span class="hljs-attr">state:</span> <span class="hljs-string">started</span>
    <span class="hljs-attr">start_mode:</span> <span class="hljs-string">auto</span>
</code></pre>
<p>Ensures PostgreSQL starts now and on reboot.</p>
<hr />
<h3 id="heading-6-tidy-up-remove-the-installer">6) Tidy up: remove the installer</h3>
<pre><code class="lang-yaml"><span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Remove</span> <span class="hljs-string">PostgreSQL</span> <span class="hljs-string">installer</span> <span class="hljs-string">if</span> <span class="hljs-string">still</span> <span class="hljs-string">present</span>
  <span class="hljs-attr">ansible.windows.win_file:</span>
    <span class="hljs-attr">path:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ pg_installer_remote_path }}</span>"</span>
    <span class="hljs-attr">state:</span> <span class="hljs-string">absent</span>
</code></pre>
<p>Keeps the host clean once installation is complete.</p>
<hr />
<h2 id="heading-post-install-verification-optional">Post-Install Verification (optional)</h2>
<p>Add one or both of these checks to confirm the install succeeded.</p>
<p><strong>Check</strong> <code>psql.exe</code> is present:</p>
<pre><code class="lang-yaml"><span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Verify</span> <span class="hljs-string">psql</span> <span class="hljs-string">exists</span>
  <span class="hljs-attr">ansible.windows.win_stat:</span>
    <span class="hljs-attr">path:</span> <span class="hljs-string">"C:\\Program Files\\PostgreSQL\\<span class="hljs-template-variable">{{ pg_major_version }}</span>\\bin\\psql.exe"</span>
  <span class="hljs-attr">register:</span> <span class="hljs-string">psql_stat</span>

<span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Report</span> <span class="hljs-string">psql</span> <span class="hljs-string">status</span>
  <span class="hljs-attr">ansible.builtin.debug:</span>
    <span class="hljs-attr">msg:</span> <span class="hljs-string">"psql.exe found: <span class="hljs-template-variable">{{ psql_stat.stat.exists }}</span>"</span>
</code></pre>
<p><strong>Check port is listening:</strong></p>
<pre><code class="lang-yaml"><span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Test</span> <span class="hljs-string">PostgreSQL</span> <span class="hljs-string">port</span>
  <span class="hljs-attr">ansible.windows.win_shell:</span> <span class="hljs-string">|
    Test-NetConnection -ComputerName localhost -Port {{ pg_port }} | Select-Object -ExpandProperty TcpTestSucceeded
</span>  <span class="hljs-attr">register:</span> <span class="hljs-string">port_test</span>

<span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Report</span> <span class="hljs-string">port</span> <span class="hljs-string">status</span>
  <span class="hljs-attr">ansible.builtin.debug:</span>
    <span class="hljs-attr">msg:</span> <span class="hljs-string">"Port <span class="hljs-template-variable">{{ pg_port }}</span> listening: <span class="hljs-template-variable">{{ port_test.stdout.strip() }}</span>"</span>
</code></pre>
<hr />
<h2 id="heading-online-alternative-with-winpackage-skip-copy-step">Online Alternative with <code>win_package</code> (skip copy step)</h2>
<p>If the Windows host has internet access, install straight from a URL:</p>
<pre><code class="lang-yaml"><span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Install</span> <span class="hljs-string">PostgreSQL</span> <span class="hljs-string">from</span> <span class="hljs-string">URL</span> <span class="hljs-string">(online</span> <span class="hljs-string">hosts)</span>
  <span class="hljs-attr">ansible.windows.win_package:</span>
    <span class="hljs-attr">path:</span> <span class="hljs-string">"https://example.com/path/to/postgresql-installer.exe"</span>   <span class="hljs-comment"># replace with the official URL</span>
    <span class="hljs-attr">arguments:</span> <span class="hljs-string">&gt;
      --mode unattended
      --unattendedmodeui none
      --superpassword "{{ pg_password }}"
      --servicename postgresql
      --serverport "{{ pg_port }}"
      --datadir "{{ pg_data_dir }}"
</span>    <span class="hljs-attr">state:</span> <span class="hljs-string">present</span>
    <span class="hljs-comment"># Use creates_path for idempotency if supported by your Ansible version:</span>
    <span class="hljs-attr">creates_path:</span> <span class="hljs-string">"C:\\Program Files\\PostgreSQL\\<span class="hljs-template-variable">{{ pg_major_version }}</span>\\bin\\psql.exe"</span>
</code></pre>
<hr />
<h2 id="heading-troubleshooting">Troubleshooting</h2>
<ul>
<li><p><strong>WinRM connection errors</strong><br />  Verify inventory variables (<code>ansible_host</code>, <code>ansible_connection=winrm</code>, <code>ansible_port</code>, <code>ansible_winrm_transport</code>, <code>ansible_user</code>, <code>ansible_password</code>) and that WinRM listeners/firewall rules are configured.</p>
</li>
<li><p><strong>Installer path issues</strong><br />  Confirm both <code>pg_installer_local_path</code> (controller) and <code>pg_installer_remote_path</code> (Windows host) are correct and accessible.</p>
</li>
<li><p><strong>UAC/permissions</strong><br />  Run with a user that has rights to install software and write to <code>C:\Program Files\</code> (or choose a different target path).</p>
</li>
<li><p><strong>Port already in use</strong><br />  If <code>5432/5433</code> is in use, change <code>pg_port</code> and re-run.</p>
</li>
<li><p><strong>Service name conflicts</strong><br />  If another PostgreSQL service exists, either uninstall it or use a unique <code>--servicename</code>.</p>
</li>
</ul>
<hr />
<h2 id="heading-conclusion">Conclusion</h2>
<p>This playbook automates a silent PostgreSQL installation on Windows, making it ideal for <strong>offline</strong> hosts where you stage the installer on the controller and copy it over. For <strong>online</strong> hosts, switch to <code>win_package</code> and install directly from a URL. The approach is repeatable, idempotent, and production-friendly with a few hardening steps (Vault, service tuning, post-install configuration).</p>
]]></content:encoded></item><item><title><![CDATA[How to Use Ansible in Windows: WinRM Setup]]></title><description><![CDATA[When people think of Ansible, they usually picture Linux servers. But Ansible can also manage Windows systems and it does so through WinRM (Windows Remote Management).
If you’re just starting with automation or DevOps, this step might sound intimidat...]]></description><link>https://devops4dummies.com/winrm-setup-guide-ansible-windows</link><guid isPermaLink="true">https://devops4dummies.com/winrm-setup-guide-ansible-windows</guid><category><![CDATA[WinRM]]></category><category><![CDATA[ansible]]></category><category><![CDATA[Windows]]></category><category><![CDATA[automation]]></category><category><![CDATA[Testing]]></category><category><![CDATA[guide]]></category><category><![CDATA[setup]]></category><category><![CDATA[Python]]></category><category><![CDATA[Powershell]]></category><category><![CDATA[Devops]]></category><category><![CDATA[sysadmin]]></category><category><![CDATA[System Configuration]]></category><category><![CDATA[IT Automation]]></category><category><![CDATA[Bash]]></category><dc:creator><![CDATA[Hardik Lokwani]]></dc:creator><pubDate>Fri, 29 Aug 2025 11:45:43 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1756899495957/599fe91c-e5af-40ca-82be-e3973d54d2f4.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<hr />
<p>When people think of Ansible, they usually picture Linux servers. But Ansible can also manage Windows systems and it does so through <strong>WinRM (Windows Remote Management)</strong>.</p>
<p>If you’re just starting with automation or DevOps, this step might sound intimidating. Don’t worry. In this guide, I’ll walk you through the <strong>easiest tested method</strong> of configuring WinRM so your Ansible control node (Linux) can talk to your Windows machines.</p>
<p>By the end, you’ll be able to run Ansible playbooks directly against Windows.</p>
<hr />
<h2 id="heading-1-requirements-on-the-ansible-control-node">1. Requirements on the Ansible Control Node</h2>
<p>First, let’s prepare the Linux machine where Ansible will run (the “control node”):</p>
<ul>
<li><p><strong>Python 3</strong> (ideally inside a virtual environment)</p>
</li>
<li><p><strong>Ansible installed</strong></p>
</li>
<li><p>The <strong>pywinrm</strong> Python package</p>
</li>
</ul>
<p>Install <code>pywinrm</code> with:</p>
<pre><code class="lang-plaintext">pip install pywinrm
</code></pre>
<p>This package allows Ansible to communicate with Windows via WinRM.</p>
<hr />
<h2 id="heading-2-create-a-windows-user-for-ansible">2. Create a Windows User for Ansible</h2>
<p>On the Windows machine, we need a dedicated user that Ansible can log in with.</p>
<p>You can create it either through the GUI or PowerShell.</p>
<p><strong>GUI method</strong>:</p>
<ul>
<li><p>Open <strong>Computer Management → Local Users and Groups → Users</strong></p>
</li>
<li><p>Right-click → <strong>New User</strong> → create a user (for example, <code>ansible_user</code>)</p>
</li>
</ul>
<p><strong>PowerShell method</strong>:</p>
<pre><code class="lang-powershell"><span class="hljs-built_in">New-LocalUser</span> <span class="hljs-literal">-Name</span> <span class="hljs-string">"ansible_user"</span> <span class="hljs-literal">-Password</span> (<span class="hljs-built_in">Read-Host</span> <span class="hljs-literal">-AsSecureString</span> <span class="hljs-string">"Enter Password"</span>) <span class="hljs-literal">-FullName</span> <span class="hljs-string">"Ansible User"</span> <span class="hljs-literal">-Description</span> <span class="hljs-string">"User for Ansible Automation"</span>
</code></pre>
<p>Next, give this user administrator rights so it can perform privileged tasks:</p>
<pre><code class="lang-powershell"><span class="hljs-built_in">Add-LocalGroupMember</span> <span class="hljs-literal">-Group</span> <span class="hljs-string">"Administrators"</span> <span class="hljs-literal">-Member</span> <span class="hljs-string">"ansible_user"</span>
</code></pre>
<hr />
<h2 id="heading-3-open-required-winrm-ports">3. Open Required WinRM Ports</h2>
<p>Ansible talks to Windows over WinRM, which uses these ports:</p>
<ul>
<li><p><strong>5985 → HTTP</strong> (default, unencrypted)</p>
</li>
<li><p><strong>5986 → HTTPS</strong> (encrypted, recommended)</p>
</li>
</ul>
<p>Make sure your firewall allows inbound traffic on these ports.</p>
<hr />
<h2 id="heading-4-configure-winrm-listeners">4. Configure WinRM Listeners</h2>
<p>Now we need to enable and configure WinRM listeners (these are what “listen” for connections).</p>
<h3 id="heading-basic-http-listener-quick-start">Basic HTTP listener (quick start)</h3>
<p>Run this in PowerShell:</p>
<pre><code class="lang-powershell"><span class="hljs-comment"># Enable WinRM service and HTTP listener</span>
<span class="hljs-built_in">Enable-PSRemoting</span> <span class="hljs-literal">-Force</span>

<span class="hljs-comment"># Open firewall for port 5985</span>
<span class="hljs-variable">$firewallParams</span> = <span class="hljs-selector-tag">@</span>{
    Action      = <span class="hljs-string">'Allow'</span>
    Description = <span class="hljs-string">'Inbound rule for Windows Remote Management via WS-Management. [TCP 5985]'</span>
    Direction   = <span class="hljs-string">'Inbound'</span>
    DisplayName = <span class="hljs-string">'Windows Remote Management (HTTP-In)'</span>
    LocalPort   = <span class="hljs-number">5985</span>
    Profile     = <span class="hljs-string">'Any'</span>
    Protocol    = <span class="hljs-string">'TCP'</span>
}
<span class="hljs-built_in">New-NetFirewallRule</span> @firewallParams

<span class="hljs-comment"># Allow local user accounts for WinRM</span>
<span class="hljs-variable">$tokenFilterParams</span> = <span class="hljs-selector-tag">@</span>{
    Path         = <span class="hljs-string">'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System'</span>
    Name         = <span class="hljs-string">'LocalAccountTokenFilterPolicy'</span>
    Value        = <span class="hljs-number">1</span>
    PropertyType = <span class="hljs-string">'DWORD'</span>
    Force        = <span class="hljs-variable">$true</span>
}
<span class="hljs-built_in">New-ItemProperty</span> @tokenFilterParams
</code></pre>
<h3 id="heading-optional-https-listener-recommended-for-security">Optional: HTTPS listener (recommended for security)</h3>
<p>If you’d prefer encrypted communication, run this additional setup:</p>
<pre><code class="lang-powershell"><span class="hljs-comment"># Create a self-signed certificate</span>
<span class="hljs-variable">$certParams</span> = <span class="hljs-selector-tag">@</span>{
    CertStoreLocation = <span class="hljs-string">'Cert:\LocalMachine\My'</span>
    DnsName           = <span class="hljs-variable">$env:COMPUTERNAME</span>
    NotAfter          = (<span class="hljs-built_in">Get-Date</span>).AddYears(<span class="hljs-number">1</span>)
    Provider          = <span class="hljs-string">'Microsoft Software Key Storage Provider'</span>
    Subject           = <span class="hljs-string">"CN=<span class="hljs-variable">$env:COMPUTERNAME</span>"</span>
}
<span class="hljs-variable">$cert</span> = <span class="hljs-built_in">New-SelfSignedCertificate</span> @certParams

<span class="hljs-comment"># Create HTTPS listener</span>
<span class="hljs-variable">$httpsParams</span> = <span class="hljs-selector-tag">@</span>{
    ResourceURI = <span class="hljs-string">'winrm/config/listener'</span>
    SelectorSet = <span class="hljs-selector-tag">@</span>{
        Transport = <span class="hljs-string">"HTTPS"</span>
        Address   = <span class="hljs-string">"*"</span>
    }
    ValueSet = <span class="hljs-selector-tag">@</span>{
        CertificateThumbprint = <span class="hljs-variable">$cert</span>.Thumbprint
        Enabled               = <span class="hljs-variable">$true</span>
    }
}
<span class="hljs-built_in">New-WSManInstance</span> @httpsParams

<span class="hljs-comment"># Open firewall for port 5986</span>
<span class="hljs-variable">$firewallParams</span> = <span class="hljs-selector-tag">@</span>{
    Action      = <span class="hljs-string">'Allow'</span>
    Description = <span class="hljs-string">'Inbound rule for Windows Remote Management via WS-Management. [TCP 5986]'</span>
    Direction   = <span class="hljs-string">'Inbound'</span>
    DisplayName = <span class="hljs-string">'Windows Remote Management (HTTPS-In)'</span>
    LocalPort   = <span class="hljs-number">5986</span>
    Profile     = <span class="hljs-string">'Any'</span>
    Protocol    = <span class="hljs-string">'TCP'</span>
}
<span class="hljs-built_in">New-NetFirewallRule</span> @firewallParams
</code></pre>
<hr />
<h2 id="heading-5-verify-the-listeners">5. Verify the Listeners</h2>
<p>Check that your listeners are active:</p>
<pre><code class="lang-powershell">winrm enumerate winrm/config/listener
</code></pre>
<p>You should see entries for port <strong>5985</strong> (HTTP) and/or <strong>5986</strong> (HTTPS).</p>
<hr />
<h2 id="heading-6-configure-your-ansible-inventory">6. Configure Your Ansible Inventory</h2>
<p>Finally, tell Ansible how to connect to the Windows host.</p>
<p>Edit your <code>inventory.ini</code> file on the control node:</p>
<pre><code class="lang-yaml">[<span class="hljs-string">windows</span>]
<span class="hljs-string">winhost01</span> <span class="hljs-string">ansible_host=192.168.1.50</span>

[<span class="hljs-string">windows:vars</span>]
<span class="hljs-string">ansible_user=ansible_user</span>
<span class="hljs-string">ansible_password=YourStrongPassword</span>
<span class="hljs-string">ansible_connection=winrm</span>
<span class="hljs-string">ansible_winrm_transport=basic</span>
<span class="hljs-string">ansible_port=5985</span>
</code></pre>
<ul>
<li><p>Replace <code>192.168.1.50</code> with your Windows machine’s IP</p>
</li>
<li><p>If you configured HTTPS, set <code>ansible_port=5986</code></p>
</li>
</ul>
<hr />
<h2 id="heading-wrapping-up">Wrapping Up</h2>
<p>That’s it—you’ve successfully set up WinRM for Ansible. With this configuration, you can now start using Ansible playbooks to automate tasks on your Windows machines.</p>
<p>If you want to dive deeper, the official Ansible documentation on Windows is a great next step.</p>
]]></content:encoded></item></channel></rss>