<?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[Ahmad Afif]]></title><description><![CDATA[Ahmad Afif]]></description><link>https://ahmadafif.com</link><generator>RSS for Node</generator><lastBuildDate>Sun, 19 Apr 2026 02:39:26 GMT</lastBuildDate><atom:link href="https://ahmadafif.com/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Citrix Licensing Manager Issue: Licenses Kicked Out in Version 11.17.2.0 Build 51000]]></title><description><![CDATA[Recently global issue happened where license is missing from License manager. When i try reapplied the license, it keeps kicking and deleting.
Issue Description

Users experience access denied
  

The Citrix License Manager interface does not show th...]]></description><link>https://ahmadafif.com/citrix-licensing-manager-issue-licenses-kicked-out-in-version-111720-build-51000</link><guid isPermaLink="true">https://ahmadafif.com/citrix-licensing-manager-issue-licenses-kicked-out-in-version-111720-build-51000</guid><category><![CDATA[License manager]]></category><category><![CDATA[Citrix]]></category><dc:creator><![CDATA[Ahmad Afif]]></dc:creator><pubDate>Mon, 03 Mar 2025 03:43:56 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1740973551060/635f0c54-69db-4168-b5b6-20f0bd3195fa.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1740972704505/0522f926-e399-40f7-a3fa-5c60c47cfa09.png" alt class="image--center mx-auto" /></p>
<p>Recently global issue happened where license is missing from License manager. When i try reapplied the license, it keeps kicking and deleting.</p>
<h3 id="heading-issue-description"><strong>Issue Description</strong></h3>
<ul>
<li><p>Users experience access denied</p>
<p>  <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1740973633136/b31d9a62-5115-4d58-97ec-21564a4b6618.png" alt class="image--center mx-auto" /></p>
</li>
<li><p>The Citrix License Manager interface does not show the expected licenses.</p>
</li>
<li><p>Applications relying on Citrix licenses become inaccessible.</p>
</li>
<li><p>The issue appears to be widespread among those using Citrix License manager version 11.17.2.0 build 51000.</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1740972951111/1f5f6db8-a289-4c38-92c7-a68f36dc790f.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-solution">Solution</h3>
<h3 id="heading-11-back-up-license-files"><strong>1.1 Back Up License Files</strong></h3>
<ul>
<li><p>Navigate to:</p>
<pre><code class="lang-powershell">  C:\Program Files (x86)\Citrix\Licensing\MyFiles
</code></pre>
</li>
<li><p>Copy all <code>.lic</code> files to a safe location.</p>
</li>
</ul>
<h3 id="heading-12-export-citrix-license-server-settings"><strong>1.2 Export Citrix License Server Settings</strong></h3>
<ul>
<li><p>Open <strong>Registry Editor</strong> (<code>regedit</code>).</p>
</li>
<li><p>Go to:</p>
<pre><code class="lang-powershell">  HKEY_LOCAL_MACHINE\SOFTWARE\Citrix\Licensing
</code></pre>
</li>
<li><p>Right-click on the <strong>Licensing</strong> folder and choose <strong>Export</strong>.</p>
</li>
<li><p>Save the file as <code>Citrix_License_Backup.reg</code>.</p>
</li>
</ul>
<h3 id="heading-13-backup-the-license-server-database"><strong>1.3 Backup the License Server Database</strong></h3>
<ul>
<li><p>Navigate to:</p>
<pre><code class="lang-powershell">  C:\Program Files (x86)\Citrix\Licensing\LS
</code></pre>
</li>
<li><p>Copy the <strong>db</strong> folder to a backup location.</p>
</li>
</ul>
<h2 id="heading-step-2-uninstall-citrix-licensing-manager"><strong>Step 2: Uninstall Citrix Licensing Manager</strong></h2>
<p>To successfully downgrade, you must <strong>remove the newer version first</strong>.</p>
<h3 id="heading-21-stop-licensing-services"><strong>2.1 Stop Licensing Services</strong></h3>
<ul>
<li><p>Open <strong>Services.msc</strong> and stop:</p>
<ul>
<li><p><strong>Citrix Licensing</strong></p>
</li>
<li><p><strong>Citrix License Management Service</strong></p>
</li>
</ul>
</li>
</ul>
<h3 id="heading-22-uninstall-citrix-licensing-manager"><strong>2.2 Uninstall Citrix Licensing Manager</strong></h3>
<ul>
<li><p>Go to <strong>Control Panel &gt; Programs and Features</strong>.</p>
</li>
<li><p>Find <strong>Citrix Licensing Manager</strong>.</p>
</li>
<li><p>Click <strong>Uninstall</strong> and follow the prompts.</p>
</li>
</ul>
<h3 id="heading-23-delete-residual-files"><strong>2.3 Delete Residual Files</strong></h3>
<p>After uninstalling, delete leftover files:</p>
<ul>
<li><p>Navigate to:</p>
<pre><code class="lang-powershell">  C:\Program Files (x86)\Citrix\Licensing
</code></pre>
</li>
<li><p>Delete any remaining files.</p>
</li>
<li><p>Remove Citrix Licensing registry entries:</p>
<pre><code class="lang-powershell">  HKEY_LOCAL_MACHINE\SOFTWARE\Citrix\Licensing
</code></pre>
</li>
<li><p>Restart your server.</p>
</li>
</ul>
<hr />
<h2 id="heading-step-3-download-and-install-version-49000-or-lower"><strong>Step 3: Download and Install Version 49000 or Lower</strong></h2>
<h3 id="heading-31-download-the-older-version"><strong>3.1 Download the Older Version</strong></h3>
<ul>
<li><p>Visit Citrix Download Center.</p>
</li>
<li><p>Search for <strong>Citrix Licensing Manager 11.16.6.0 build 49000</strong> or your preferred older version.</p>
</li>
<li><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1740973284675/2b29fb55-6773-4ecf-8e8b-7e51a23d57e4.png" alt class="image--center mx-auto" /></p>
</li>
<li><p>Download the <strong>.exe installer</strong>.</p>
</li>
</ul>
<h3 id="heading-32-install-the-older-version"><strong>3.2 Install the Older Version</strong></h3>
<ul>
<li><p>Run the installer with <strong>Administrator privileges</strong>.</p>
</li>
<li><p>Follow the setup wizard and complete the installation.</p>
</li>
<li><p>Restart your server after installation.</p>
</li>
</ul>
<hr />
<h2 id="heading-step-4-restore-backups-and-reconfigure"><strong>Step 4: Restore Backups and Reconfigure</strong></h2>
<h3 id="heading-41-restore-license-files"><strong>4.1 Restore License Files</strong></h3>
<ul>
<li><p>Copy the <strong>.lic</strong> files from your backup to:</p>
<pre><code class="lang-powershell">  C:\Program Files (x86)\Citrix\Licensing\MyFiles
</code></pre>
</li>
</ul>
<h3 id="heading-42-restore-registry-settings"><strong>4.2 Restore Registry Settings</strong></h3>
<ul>
<li><p>Double-click the <code>Citrix_License_Backup.reg</code> file.</p>
</li>
<li><p>Confirm the merge.</p>
</li>
</ul>
<h3 id="heading-43-restore-database-files"><strong>4.3 Restore Database Files</strong></h3>
<ul>
<li><p>Copy the backed-up <strong>db</strong> folder to:</p>
<pre><code class="lang-powershell">  C:\Program Files (x86)\Citrix\Licensing\LS
</code></pre>
</li>
</ul>
<hr />
<h2 id="heading-step-5-restart-services-and-verify"><strong>Step 5: Restart Services and Verify</strong></h2>
<h3 id="heading-51-restart-licensing-services"><strong>5.1 Restart Licensing Services</strong></h3>
<ul>
<li><p>Open <strong>Services.msc</strong>.</p>
</li>
<li><p>Start:</p>
<ul>
<li><p><strong>Citrix Licensing</strong></p>
</li>
<li><p><strong>Citrix License Management Service</strong></p>
</li>
</ul>
</li>
</ul>
<h3 id="heading-52-verify-license-availability"><strong>5.2 Verify License Availability</strong></h3>
<ul>
<li><p>Open <strong>Citrix Licensing Manager</strong> via:</p>
<pre><code class="lang-powershell">  https://&lt;your<span class="hljs-literal">-license</span><span class="hljs-literal">-server</span>&gt;:<span class="hljs-number">8083</span>
</code></pre>
</li>
<li><p>Ensure licenses are recognized and assigned correctly.</p>
</li>
</ul>
<h3 id="heading-53-test-license-assignment"><strong>5.3 Test License Assignment</strong></h3>
<ul>
<li><p>Run:</p>
<pre><code class="lang-powershell">  lmstat <span class="hljs-literal">-a</span> <span class="hljs-literal">-c</span> <span class="hljs-selector-tag">@</span>&lt;LicenseServerName&gt;
</code></pre>
</li>
<li><p>Confirm licenses are displayed.</p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[No License? No Problem! Ways to Continue Using Citrix Studio]]></title><description><![CDATA[As an average man. Couldn’t afford Citrix License. Luckily Citrix give trial 30 days for me to leverage my lab environment. Once it over, i need to start again build the Citrix Delivery Controller (DDC)
There is 2 options for renewal.
Beta-License Re...]]></description><link>https://ahmadafif.com/no-license-no-problem-ways-to-continue-using-citrix-studio</link><guid isPermaLink="true">https://ahmadafif.com/no-license-no-problem-ways-to-continue-using-citrix-studio</guid><category><![CDATA[Citrix studio]]></category><category><![CDATA[Citrix]]></category><dc:creator><![CDATA[Ahmad Afif]]></dc:creator><pubDate>Sun, 23 Feb 2025 14:56:15 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1740322393582/42ad4096-73e2-45e3-94ae-55649e659507.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>As an average man. Couldn’t afford Citrix License. Luckily Citrix give trial 30 days for me to leverage my lab environment. Once it over, i need to start again build the Citrix Delivery Controller (DDC)</p>
<p>There is 2 options for renewal.</p>
<h3 id="heading-beta-license-renewal">Beta-License Renewal</h3>
<p>Get your license when you have Citrix account. Go to Previews/Betas - License Retrieaval</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1740195225203/d2f982b8-74fa-45da-aa60-5e74fd8ead4a.png" alt class="image--center mx-auto" /></p>
<p>Update the license in Citrix Studio. Go to Licensing &gt; Allocate License and enter the serial number</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1740321892033/ccef1983-ccca-422a-9c78-7a50d1d9584e.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-reinstall-again-citrix-studio">Reinstall again Citrix studio.</h3>
<p>Uninstall expired Delivery Controller. and Install again ISO.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1740318491307/e17fac58-8d4e-4c60-a1f1-0bcf403ba73b.png" alt class="image--center mx-auto" /></p>
<p>Mount ISO and install again new Citrix Studio</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1740318862375/75af3f5e-0f49-4b29-987b-2c06d1275320.png" alt class="image--center mx-auto" /></p>
<p>Normally i will choose Virtual Apps and Desktop</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1740319028727/041e47e7-24c7-4ced-a7d3-2f204284942c.png" alt class="image--center mx-auto" /></p>
<p>Choose default</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1740318529153/278e0dfe-7d85-4cef-b107-749a070234f6.png" alt class="image--center mx-auto" /></p>
<p>By right, it will not show expired anymore once reinstall finished</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1740322188794/5c4c9ba6-6e6b-43bc-93b2-3383bc3bb3c1.png" alt class="image--center mx-auto" /></p>
]]></content:encoded></item><item><title><![CDATA[Step-by-Step Guide: SSL Certificate Renewal in Citrix ADC (NetScaler)]]></title><description><![CDATA[Overview
SSL certificates are crucial for securing communications between clients and servers by encrypting data and ensuring authenticity. SSL certificates have expiration dates, typically every 1 to 2 years, to maintain security standards and compl...]]></description><link>https://ahmadafif.com/step-by-step-guide-ssl-certificate-renewal-in-citrix-adc-netscaler</link><guid isPermaLink="true">https://ahmadafif.com/step-by-step-guide-ssl-certificate-renewal-in-citrix-adc-netscaler</guid><category><![CDATA[Citrix NetScaler ADC]]></category><category><![CDATA[netscaler]]></category><category><![CDATA[Citrix]]></category><dc:creator><![CDATA[Ahmad Afif]]></dc:creator><pubDate>Fri, 21 Feb 2025 09:42:09 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1740130808776/a86ab140-b073-4605-84e7-885f040d3025.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-overview">Overview</h2>
<p>SSL certificates are crucial for securing communications between clients and servers by encrypting data and ensuring authenticity. SSL certificates have expiration dates, typically every 1 to 2 years, to maintain security standards and compliance.</p>
<p>Failing to renew an SSL certificate can lead to <strong>service disruptions, security warnings, and potential loss of trust from users</strong>.</p>
<p>Generate a New CSR (If Required)</p>
<ol>
<li><p>Log in to <strong>Citrix ADC GUI</strong>.</p>
</li>
<li><p>Navigate to <strong>Traffic Management &gt; SSL &gt; SSL Files</strong>.</p>
</li>
<li><p>Click on <strong>Create RSA Key</strong>:</p>
<ul>
<li><p>Key Filename: <code>mydomain.key</code></p>
</li>
<li><p>Key Size: <code>2048</code> (recommended)</p>
</li>
<li><p>PEM Encoding: <code>Enable</code></p>
</li>
</ul>
</li>
<li><p>Click <strong>Create</strong>.</p>
</li>
<li><p>Generate a CSR:</p>
<ul>
<li><p>Go to <strong>SSL &gt; SSL Files &gt; Create CSR</strong>.</p>
</li>
<li><p>Select the RSA key file you just created.</p>
</li>
<li><p>Fill in the required details (Common Name, Organization, Country, etc.).</p>
</li>
<li><p>Click <strong>Create</strong>.</p>
</li>
<li><p>Download the CSR file and submit it to the CA.</p>
</li>
</ul>
</li>
</ol>
<p><strong>Locate the Expiring Certificate</strong></p>
<ol>
<li><p><strong>Login to Citrix ADC GUI</strong>.</p>
</li>
<li><p><strong>Go to:</strong></p>
<ul>
<li>Traffic Management &gt; SSL &gt; Server Certificates</li>
</ul>
</li>
<li><p>Locate the <strong>expiring certificate</strong>.</p>
</li>
<li><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1740129489718/33e2d7bf-604d-417b-8849-f7c518e197db.png" alt class="image--center mx-auto" /></p>
<p> Hover over the <strong>certificate</strong>, click the <strong>"3 dots" menu</strong>, and select <strong>"Show Bindings"</strong>.</p>
</li>
<li><p><strong>Record Certificate Bindings:</strong> Take a <strong>screenshot</strong> or note down all <strong>bound services</strong></p>
</li>
<li><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1740129608481/1e19168b-3300-444a-aae4-2ee5afe1de00.png" alt class="image--center mx-auto" /></p>
<p> Can check validity of public certificates by renaming the certificate to .cer. Example: <em>25-26</em>-PublicCert.pem to .cer</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1740129701892/6e45e2de-7ac3-42ac-91d7-1b2e6aec23de.png" alt class="image--center mx-auto" /></p>
<p> <strong>Upload Certificate to NetScaler</strong></p>
<ol>
<li><p>Navigate to:</p>
<ul>
<li>Traffic Management &gt; SSL &gt;  Certificates &gt;  All Certificates</li>
</ul>
</li>
<li><p>Click Install.</p>
</li>
<li><p>Enter the details</p>
<ul>
<li><p>Certificate-Key Pair Name: [any suitable name]</p>
</li>
<li><p>Certificate File Name: [certificate either in <strong>crt, PEM</strong> etc]</p>
</li>
<li><p>Key File Name: [<strong>private key file</strong> used to generate the certificate.]</p>
</li>
<li><p>Certificate format: PEM</p>
<p>  Citrix ADC primarily uses PEM format, which contains:</p>
<ul>
<li><p>Certificate (<code>.crt</code> or <code>.pem</code>)</p>
</li>
<li><p>Private Key (<code>.key</code>)</p>
</li>
<li><p>Intermediate CA Certificates (if applicable)</p>
</li>
</ul>
</li>
</ul>
</li>
</ol>
</li>
</ol>
<p>    <strong>Bind the Certificate to Virtual Servers</strong></p>
<ol>
<li><p>Go to:</p>
<ul>
<li>Traffic Management &gt; Citrix Gateway &gt; Virtual Servers</li>
</ul>
</li>
<li><p>Identify the virtual server using the expiring certificate.</p>
</li>
<li><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1740130068522/a4662a05-875f-4102-ae68-1f65d538d547.png" alt class="image--center mx-auto" /></p>
<p> Hover over it, click “3 dots”, select "Edit".</p>
</li>
<li><p>Click "1 Server Certificate" to open Certificate Bindings.</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1740130188740/fd164a68-2819-4753-a363-cbaafc992723.png" alt class="image--center mx-auto" /></p>
</li>
<li><p>Unbind the old certificate:</p>
<ul>
<li>Hover over the old certificate, click "3 dots" → Select Unbind.</li>
</ul>
</li>
<li><p>Bind the new certificate: Click Add Binding</p>
</li>
<li><p>Select the newly installed certificate.</p>
</li>
<li><p>Click OK &gt; Save Changes</p>
</li>
</ol>
<p>    <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1740130259778/2504a29e-2454-4974-bcfc-5117fed9c7ae.png" alt class="image--center mx-auto" /></p>
<p>    <strong>Update SSL Gateway Virtual Servers</strong></p>
<ol>
<li><p>Navigate to:</p>
<ul>
<li>Configuration &gt; Citrix Gateway &gt; Citrix Gateway Virtual Servers</li>
</ul>
</li>
<li><p>Locate SSL Gateway using the old certificate.</p>
</li>
<li><p>Click Edit → Select Certificate Details.</p>
</li>
<li><p>Bind the new one</p>
</li>
</ol>
<p>    <strong>Validate and Verify Certificate Renewal</strong></p>
<p>    Run the following command: <code>show ssl certKey [Certificate key-pair name]</code> Ensure the expiry date is correct.</p>
<p>    <strong>Test SSL Connectivity</strong></p>
<ol>
<li><p>Open a web browser.</p>
</li>
<li><p>Navigate to the website, click the lock icon, and view certificate details.</p>
</li>
</ol>
<p>    <strong><em>openssl s_client -connect</em></strong> <a target="_blank" href="http://yourdomain.com:443"><strong><em>yourdomain.com:443</em></strong></a> <strong><em>-servername</em></strong> <a target="_blank" href="http://yourdomain.com"><strong><em>yourdomain.com</em></strong></a></p>
<h3 id="heading-sync-to-secondary-adc-force-sync">Sync to Secondary ADC [force sync]</h3>
<p><code>show ha node</code></p>
<p><code>sync ha files</code></p>
<p><code>force ha sync</code></p>
<p><code>show ha node</code></p>
]]></content:encoded></item><item><title><![CDATA[Upgrade Citrix NetScaler from 13 to 14.1]]></title><description><![CDATA[Overview:
Upgrading Citrix NetScaler is essential for:
• Security Enhancements: Patches and fixes for known vulnerabilities.
• Performance Improvements: Optimized traffic management and processing.
• New Features: Access to advanced networking and se...]]></description><link>https://ahmadafif.com/how-to-upgrade-citrix-netscaler-13-to-14</link><guid isPermaLink="true">https://ahmadafif.com/how-to-upgrade-citrix-netscaler-13-to-14</guid><category><![CDATA[Citrix NetScaler ADC]]></category><category><![CDATA[netscaler]]></category><category><![CDATA[Citrix]]></category><dc:creator><![CDATA[Ahmad Afif]]></dc:creator><pubDate>Fri, 21 Feb 2025 09:00:48 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1740128231612/eaa45155-cc55-4e55-a521-e771bf73a83a.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-overview">Overview:</h2>
<p>Upgrading Citrix NetScaler is essential for:</p>
<p>• Security Enhancements: Patches and fixes for known vulnerabilities.</p>
<p>• Performance Improvements: Optimized traffic management and processing.</p>
<p>• New Features: Access to advanced networking and security functionalities.</p>
<p>• Compatibility: Ensures seamless integration with newer Citrix infrastructure and third-party applications.</p>
<p>• Bug Fixes: Resolves known issues and improves system stability.</p>
<p>While Citrix Netscaler provide GUI based upgrade process, it is not recommended method due to several factors like stability and reliability. GUI is known to occasionally free, time out or fail. It has higher risk of corrupting the system due to not properly clean up old files.</p>
<p>Based on experience, success rate upgrade using GUI is around 70%++ only.</p>
<h2 id="heading-upgrade-netscaler-implementation-plan">Upgrade NetScaler Implementation Plan</h2>
<p>Pre-Upgrade Planning:</p>
<p>• Before upgrading, create a full virtual machine (VM) snapshot if NetScaler is running on a virtualized environment. This ensures a quick recovery in case of upgrade failure. Ensure sufficient storage space for the snapshot.</p>
<p>• Log in to vCenter.</p>
<p>• Locate your NetScaler virtual machine.</p>
<p>• Create a snapshot for the NetScaler</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1740127062556/78f214d7-64a2-4981-9dc4-1c9a63e32430.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-create-full-backup-via-web-interface">Create Full Backup via Web Interface</h2>
<p>• Log in to NetScaler using your web browser.</p>
<p>• Navigate to Configuration &gt; System &gt; Backup and Restore.</p>
<p>• For naming format: File name [DD-MM-YY], select full in level option</p>
<p>• Create a full backup and download it to your local machine</p>
<p>Filename: [date]</p>
<p>level: Full</p>
<p>Comment: Any reference like CHG number</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1740127180538/37229598-5216-48a1-ab7f-9bacb0a07008.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-backup-nsconf-using-winscp">Backup ns.conf using WinSCP</h2>
<p>Open WinSCP.</p>
<p>• Enter the IP address of your NetScaler, username, and password. Use SCP protocol</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1740127258091/b96180b4-6ce3-4fbc-86cc-850ae4a66634.png" alt class="image--center mx-auto" /></p>
<p>• Click "Login".</p>
<p>• Navigate to the <code>/flash/nsconfig</code> directory.</p>
<p>• Backup the ns.conf file by copying it to your local machine</p>
<p>• This backup allows quick restoration of the primary configuration file in case of errors</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1740127336402/f91267fd-f307-41da-a7c4-caa533c43de6.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-upgrade-netscaler">Upgrade NetScaler</h2>
<p>• In WinSCP, navigate to the <code>/var/nsinstall/Builds</code> directory</p>
<p>• Create a new folder named after the firmware version you are upgrading to. Normally need to download the firmware from <a target="_blank" href="https://www.citrix.com/downloads/">https://www.citrix.com/downloads/</a></p>
<p>• Upload the firmware file to this newly created folder.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1740127392959/8cf0c6ea-198c-4012-88b1-9949e31fa21e.png" alt class="image--center mx-auto" /></p>
<p>• Open putty and Enter the IP address of your NetScaler</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1740127519736/14e4fa19-3797-47d0-a8c3-db5a69669ccd.png" alt class="image--center mx-auto" /></p>
<p>• Enter the IP address of your NetScaler and connect</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1740127554404/c2aebdee-e48a-43aa-bb09-c8847402f060.png" alt class="image--center mx-auto" /></p>
<p>Access Shell:</p>
<p>• Once connected, type shell to access the shell prompt</p>
<p>Check Disk Space:</p>
<p>• Type <code>df -h</code> to view the available disk space.</p>
<p>• Ensure the /var partition has at least 4GB of free space.</p>
<p>Navigate to Firmware Directory:</p>
<p>• Type <code>cd /var/nsinstall/</code> to navigate to the firmware directory.</p>
<p>• Type ls to see all the list that available</p>
<p>Extract Firmware:</p>
<p>• Type <code>tar -xvzf</code> to extract the firmware files.</p>
<p>It stands for:</p>
<p>x → Extract: This tells tar to extract files from an archive.</p>
<p>v → Verbose: Displays the files being extracted, providing progress output.</p>
<p>z → Gzip: Specifies that the archive is compressed using gzip (.tgz or .tar.gz format).</p>
<p>f → File: Indicates that the next argument is the filename of the archive.</p>
<p>• Type ls -tl once the extraction completed to see list is available</p>
<p>Install Firmware:</p>
<p>• Type <code>installns</code> to start the firmware installation process.tar</p>
<p>• Respond to any confirmation prompts as needed. Delete old signature files and kernel images if asked</p>
<p>Wait for Reboot:</p>
<p>• The NetScaler will automatically reboot once the firmware installation is complete.</p>
<p>• Can monitor the process at the vCenter</p>
<p>Verify Firmware Version:</p>
<p>• After the reboot, log in to the NetScaler via the web interface.</p>
<p>• Make sure it does not change to freemium</p>
<p>• Check the firmware version by clicking on the nsroot account in the top right corner.</p>
<p>• Can also try validation by launching the storefront</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1740127821496/5241cb7c-b6f2-4229-b62b-096b1c6e4289.png" alt class="image--center mx-auto" /></p>
<p>Ensure 4GB of /var disk space is available. By checking on the configuration &gt; System Upgrade &gt; Check Disk Space</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1740127850600/a2cddcbb-a789-471a-8689-c78e4cea2aa2.png" alt class="image--center mx-auto" /></p>
]]></content:encoded></item><item><title><![CDATA[Create and Publish a Python Random Password Generator Web App for Free]]></title><description><![CDATA[At my company, we’re required to change our passwords every month, so I thought, why not create my own password generator?
Initially, I used Flask to build a simple password generator. However, I found it a bit complex since it required creating sepa...]]></description><link>https://ahmadafif.com/create-and-publish-a-python-random-password-generator-web-app-for-free</link><guid isPermaLink="true">https://ahmadafif.com/create-and-publish-a-python-random-password-generator-web-app-for-free</guid><category><![CDATA[Python]]></category><category><![CDATA[streamlit]]></category><dc:creator><![CDATA[Ahmad Afif]]></dc:creator><pubDate>Tue, 13 Aug 2024 03:36:53 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/iar-afB0QQw/upload/c904178b47c2a8c51e2d0c530f01d344.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>At my company, we’re required to change our passwords every month, so I thought, why not create my own password generator?</p>
<p>Initially, I used Flask to build a simple password generator. However, I found it a bit complex since it required creating separate HTML files for the website.</p>
<p>Then I discovered Streamlit, which made the process much easier. With Streamlit, you can focus on your Python code and let it handle the web interface for you.</p>
<p>In this post, I'll first walk you through the code, and then I'll show you how to create a free web app using it.</p>
<p>At first, will show the algorithm for password generator. Its pretty straight forward.</p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">generate_random_password</span>(<span class="hljs-params">length=<span class="hljs-number">12</span></span>):</span>
    <span class="hljs-comment"># string.ascii_letters is for lowercase and uppercase letters</span>
    <span class="hljs-comment"># string.digits is for numbers</span>
    <span class="hljs-comment"># string.punctuation is special characters</span>
    characters = string.ascii_letters + string.digits + string.punctuation
    password = <span class="hljs-string">''</span>.join(random.choice(characters) <span class="hljs-keyword">for</span> index <span class="hljs-keyword">in</span> range(length))
<span class="hljs-keyword">return</span> password
</code></pre>
<p>For the function, It starts with an empty string (<code>''</code>) and adds characters to it using the <code>join()</code> function.</p>
<p><code>for index in range(length))</code> used to pick up random characters till the length is achieved.</p>
<p>That’s the basic idea. Now, let’s move on to the fun part: making a web app.</p>
<p><a target="_blank" href="https://passwordgeneratorapp24.streamlit.app/">Website link</a></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1723518221239/0630bda1-8334-40b2-bbeb-8c7d59c02232.png" alt class="image--center mx-auto" /></p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> random
<span class="hljs-keyword">import</span> string
<span class="hljs-keyword">import</span> time
<span class="hljs-keyword">import</span> streamlit <span class="hljs-keyword">as</span> st

funny_quotes = [
    <span class="hljs-string">"This password is so strong, it does push-ups!"</span>,
    <span class="hljs-string">"Even your mom can't guess this one!"</span>,
    <span class="hljs-string">"MCMC is watching"</span>,
    <span class="hljs-string">"Guard this password like it's your last slice of pizza!"</span>,
    <span class="hljs-string">"Better write this one down—it's practically a masterpiece!"</span>,
    <span class="hljs-string">"Your password is more complicated than assembling IKEA furniture."</span>,
    <span class="hljs-string">"Your password is now as secretive as a magician’s tricks."</span>,
    <span class="hljs-string">"This password is so good, even the MCMC would be impressed."</span>,
    <span class="hljs-string">"This password is so secure, even Sherlock Holmes couldn’t deduce it."</span>,
    <span class="hljs-string">"This password is more complicated than your love life."</span>,
    <span class="hljs-string">"This password is stronger than your morning coffee."</span>,
    <span class="hljs-string">"This password is more mysterious than the Bermuda Triangle."</span>,
    <span class="hljs-string">"This password is harder to guess than the plot twist in a Momento."</span>,
    <span class="hljs-string">"This password is harder to figure out than the meaning of life."</span>,
    <span class="hljs-string">"This password is more confusing than your partner."</span>,
    <span class="hljs-string">"This password is more secure than your emotional walls."</span>,
]

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">generate_random_password</span>(<span class="hljs-params">length=<span class="hljs-number">12</span></span>):</span>
    characters = string.ascii_letters + string.digits + string.punctuation
    password = <span class="hljs-string">''</span>.join(random.choice(characters) <span class="hljs-keyword">for</span> _ <span class="hljs-keyword">in</span> range(length))
    <span class="hljs-keyword">return</span> password

st.title(<span class="hljs-string">"Random Password Generator"</span>)

length = st.slider(<span class="hljs-string">"Select password length"</span>, min_value=<span class="hljs-number">8</span>, max_value=<span class="hljs-number">30</span>, value=<span class="hljs-number">12</span>)

<span class="hljs-keyword">if</span> st.button(<span class="hljs-string">"Generate Password"</span>):
    password = generate_random_password(length)
    st.write(<span class="hljs-string">"Generated password:"</span>)

    <span class="hljs-comment"># Display password in a text input for easy copying</span>
    st.header(password)

    <span class="hljs-comment"># Temporary pop-up message for success</span>
    success_placeholder = st.empty()
    success_placeholder.success(<span class="hljs-string">"Password generated successfully."</span>)
    st.balloons()

    <span class="hljs-comment"># Wait for a few seconds</span>
    time.sleep(<span class="hljs-number">3</span>)

    st.write(random.choice(funny_quotes))

    <span class="hljs-comment"># Clear the success message</span>
    success_placeholder.empty()

<span class="hljs-comment"># Simple tagline below the page</span>
st.write(<span class="hljs-string">"\n"</span> * <span class="hljs-number">40</span>)  <span class="hljs-comment"># Adding some space before the footer</span>
st.write(<span class="hljs-string">"ahmadafif.com"</span>, align=<span class="hljs-string">"center"</span>, font_size=<span class="hljs-number">17</span>)
</code></pre>
<p>For the library we will import few:</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> random
<span class="hljs-keyword">import</span> string
<span class="hljs-keyword">import</span> time
<span class="hljs-keyword">import</span> streamlit <span class="hljs-keyword">as</span> st
</code></pre>
<p><code>random</code> and <code>string</code>: to generate random characters and string for password</p>
<p><code>time:</code> for brief delay before display quote</p>
<p><code>streamlit</code>: library that create interactive web app</p>
<p>you can install the library by using this command:</p>
<p><code>pip install streamlit</code></p>
<p>At first, I hard-coded the funny quotes like below</p>
<pre><code class="lang-python">funny_quotes = [
    <span class="hljs-string">"This password is so strong, it does push-ups!"</span>,
    ...
]
</code></pre>
<p>password generator is the same as explained earlier</p>
<p>for web interface:</p>
<pre><code class="lang-python">st.title(<span class="hljs-string">"Random Password Generator"</span>)
length = st.slider(<span class="hljs-string">"Select password length"</span>, min_value=<span class="hljs-number">8</span>, max_value=<span class="hljs-number">30</span>, value=<span class="hljs-number">12</span>)
</code></pre>
<p><strong>Title</strong>: The app is titled "Random Password Generator".</p>
<p><strong>Password Length Slider</strong>: You can choose the length of the password using a slider, with options ranging from 8 to 30 characters.</p>
<p><strong>Generate Button and Display</strong>:</p>
<pre><code class="lang-python"><span class="hljs-keyword">if</span> st.button(<span class="hljs-string">"Generate Password"</span>):
    password = generate_random_password(length)
    st.write(<span class="hljs-string">"Generated password:"</span>)
    st.header(password)
</code></pre>
<p><strong>Button</strong>: When you click the "Generate Password" button, the app creates a new password.</p>
<p><strong>Display</strong>: The generated password is shown on the screen, ready for you to copy.</p>
<p><strong>Fun Extras</strong>:</p>
<pre><code class="lang-python">success_placeholder = st.empty()
success_placeholder.success(<span class="hljs-string">"Password generated successfully."</span>)
st.balloons()
time.sleep(<span class="hljs-number">3</span>)
st.write(random.choice(funny_quotes))
success_placeholder.empty()
</code></pre>
<p><strong>Success Message and Balloons</strong>: After generating the password, a success message appears, and fun balloons fill the screen.</p>
<p><strong>Funny Quote</strong>: After a brief delay, a random funny quote from the list is displayed, adding a bit of humor to the experience.</p>
<h3 id="heading-how-to-upload-to-web-apps">How to upload to web apps</h3>
<ol>
<li><h3 id="heading-run-in-local">Run in local</h3>
<p> Before deploying, you should test your app locally to ensure everything works. In the terminal or command prompt, navigate to the directory where your <code>[filename]</code><a target="_blank" href="http://generator.py"><code>.py</code></a> file is located and run:</p>
</li>
</ol>
<h3 id="heading-streamlit-run-filenamepy"><code>streamlit run [filename].py</code></h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1723519118107/b09c0815-9816-4852-8531-0a66ed9872cc.png" alt class="image--center mx-auto" /></p>
<p>This command will open a new tab in your web browser where you can see your app in action.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1723519146245/e3187ea5-aca2-48e0-8e00-dbd644548388.png" alt class="image--center mx-auto" /></p>
<ol start="2">
<li><h3 id="heading-create-a-github-repository">Create a GitHub Repository</h3>
<p> Go to your GitHub profile and click on "New" to create a new repository.</p>
<p> Name your repository (e.g., <code>PasswordGeneratorStreamlite</code>) and choose to make it public.</p>
<p> Upload your code.</p>
<p> It will be something like this:</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1723519437482/d8ed468b-54e7-46e0-a389-78613e691faf.png" alt class="image--center mx-auto" /></p>
</li>
<li><h3 id="heading-deploy-app-to-streamlit"><strong>Deploy app to Streamlit</strong></h3>
<p> visit website and login using Github account (prefferable)</p>
<p> Click on "New app" and select the GitHub repository you just created.</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1723519715375/0bf53a57-b679-43f2-8ad0-e369b0378224.png" alt class="image--center mx-auto" /></p>
<p> Choose the branch (usually <code>main</code> or <code>master</code>) and the Python file (e.g <code>BasePassGen.py</code>).</p>
<p> Click "Deploy".</p>
<p> Congratulations! Your app is ready.</p>
</li>
</ol>
]]></content:encoded></item><item><title><![CDATA[Reboot Multiple Servers Without Script]]></title><description><![CDATA[I think this method is faster rather than executing lengthy powershell code.  
1. Go to Command Prompt (CMD)2. Type "shutdown -i"3. Add servers, computer list that you want to shutdown or restart4. Put comment why restart5. Enter ok.]]></description><link>https://ahmadafif.com/reboot-multiple-servers-without-script</link><guid isPermaLink="true">https://ahmadafif.com/reboot-multiple-servers-without-script</guid><category><![CDATA[server]]></category><category><![CDATA[windows server]]></category><dc:creator><![CDATA[Ahmad Afif]]></dc:creator><pubDate>Thu, 08 Aug 2024 12:47:17 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/PSpf_XgOM5w/upload/68cf74e6e4978fb0dc106ba89a58e41d.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I think this method is faster rather than executing lengthy powershell code.  </p>
<p>1. Go to Command Prompt (CMD)<br />2. Type "shutdown -i"<br />3. Add servers, computer list that you want to shutdown or restart<br />4. Put comment why restart<br />5. Enter ok.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1723120686783/3d2a00dd-84dc-4fd4-9577-3fe0732940ea.png" alt class="image--center mx-auto" /></p>
]]></content:encoded></item><item><title><![CDATA[Verify and Start the Citrix Profile Management Service]]></title><description><![CDATA[I created simple PowerShell script that checks whether the Citrix Profile Management service is running on multiple servers and starts it if it's no
$list = Get-Location
$serverListPath = "$list\citrixProfileManagementList.txt" #create one later file...]]></description><link>https://ahmadafif.com/verify-and-start-the-citrix-profile-management-service</link><guid isPermaLink="true">https://ahmadafif.com/verify-and-start-the-citrix-profile-management-service</guid><category><![CDATA[Citrix]]></category><category><![CDATA[Powershell]]></category><dc:creator><![CDATA[Ahmad Afif]]></dc:creator><pubDate>Wed, 07 Aug 2024 02:31:46 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1722998037969/401d1b18-e76e-43ab-85af-085b27179493.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I created simple PowerShell script that checks whether the Citrix Profile Management service is running on multiple servers and starts it if it's no</p>
<pre><code class="lang-powershell"><span class="hljs-variable">$list</span> = <span class="hljs-built_in">Get-Location</span>
<span class="hljs-variable">$serverListPath</span> = <span class="hljs-string">"<span class="hljs-variable">$list</span>\citrixProfileManagementList.txt"</span> <span class="hljs-comment">#create one later file</span>


<span class="hljs-variable">$logFilePath</span> = <span class="hljs-string">"<span class="hljs-variable">$list</span>\serviceLog.log"</span> <span class="hljs-comment">#will create later</span>


<span class="hljs-variable">$logentries</span> = <span class="hljs-selector-tag">@</span>()
<span class="hljs-variable">$sessions</span> = <span class="hljs-selector-tag">@</span>()


<span class="hljs-variable">$servers</span> = <span class="hljs-built_in">Get-Content</span> <span class="hljs-literal">-Path</span> <span class="hljs-variable">$serverListPath</span>


<span class="hljs-keyword">foreach</span>(<span class="hljs-variable">$server</span> <span class="hljs-keyword">in</span> <span class="hljs-variable">$servers</span>){
    <span class="hljs-keyword">try</span> {
        <span class="hljs-variable">$session</span> = <span class="hljs-built_in">New-PSSession</span> <span class="hljs-literal">-ComputerName</span> <span class="hljs-variable">$server</span> <span class="hljs-literal">-ErrorAction</span> Stop
        <span class="hljs-variable">$sessions</span> += <span class="hljs-variable">$session</span>
    }
    <span class="hljs-keyword">catch</span>{
        <span class="hljs-variable">$logentry</span> = <span class="hljs-string">"Server: <span class="hljs-variable">$server</span> - Error to create session - <span class="hljs-variable">$_</span>"</span>
        <span class="hljs-variable">$logentry</span> += <span class="hljs-variable">$logentry</span>
        <span class="hljs-built_in">Write-Host</span> <span class="hljs-variable">$logentry</span>
    }

}

<span class="hljs-keyword">foreach</span>(<span class="hljs-variable">$session</span> <span class="hljs-keyword">in</span> <span class="hljs-variable">$sessions</span>){
    <span class="hljs-variable">$serverName</span> = <span class="hljs-variable">$session</span>.ComputerName
    <span class="hljs-keyword">try</span>{
        <span class="hljs-variable">$checkService</span> = <span class="hljs-built_in">Invoke-Command</span> <span class="hljs-literal">-Session</span> <span class="hljs-variable">$session</span> <span class="hljs-literal">-ScriptBlock</span> { <span class="hljs-built_in">Get-Service</span> <span class="hljs-literal">-Name</span> <span class="hljs-string">"ctxprofile"</span> <span class="hljs-literal">-ErrorAction</span> stop}  

        <span class="hljs-keyword">if</span>(<span class="hljs-variable">$checkService</span>.status <span class="hljs-operator">-eq</span> <span class="hljs-string">"running"</span>){
            <span class="hljs-variable">$logentry</span> = <span class="hljs-string">"server <span class="hljs-variable">$serverName</span>  - service ctxprofile is running"</span>
        }
        <span class="hljs-keyword">else</span> {<span class="hljs-variable">$logentry</span> = <span class="hljs-string">"server <span class="hljs-variable">$serverName</span>  - service ctxprofile is not running"</span> }

    }
    <span class="hljs-keyword">catch</span>{
        <span class="hljs-variable">$logentry</span> = <span class="hljs-string">"Service ctxProfile not found on server: <span class="hljs-variable">$server</span> -error: <span class="hljs-variable">$_</span>"</span>
    }

    <span class="hljs-variable">$logEntries</span> += <span class="hljs-variable">$logentry</span>
    <span class="hljs-built_in">Write-Host</span> <span class="hljs-variable">$logentry</span>
}

<span class="hljs-comment">#need to close all Pssessions</span>
<span class="hljs-comment">&lt;#$sessions | ForEach-Object {
    Remove-PSSession -Session $_
} #&gt;</span>

<span class="hljs-comment">#or can use like this</span>
<span class="hljs-keyword">foreach</span>(<span class="hljs-variable">$session</span> <span class="hljs-keyword">in</span> <span class="hljs-variable">$sessions</span>){
    <span class="hljs-built_in">Remove-PSSession</span> <span class="hljs-literal">-Session</span> <span class="hljs-variable">$session</span>
}


<span class="hljs-variable">$logentries</span> | <span class="hljs-built_in">Out-File</span> <span class="hljs-literal">-FilePath</span> <span class="hljs-variable">$logFilePath</span> <span class="hljs-literal">-Force</span>
<span class="hljs-variable">$logentries</span> | <span class="hljs-built_in">Out-GridView</span> <span class="hljs-literal">-Title</span> <span class="hljs-string">"Service Check Results"</span>
</code></pre>
<p>For checking the service is running only and outcome in Out-Grid View (OGV), can use this powershell script</p>
<pre><code class="lang-powershell"><span class="hljs-variable">$list</span> = <span class="hljs-built_in">Get-Location</span>
<span class="hljs-variable">$serverListPath</span> = <span class="hljs-string">"<span class="hljs-variable">$list</span>\citrixProfileManagementList.txt"</span> <span class="hljs-comment">#create one later file</span>


<span class="hljs-variable">$logFilePath</span> = <span class="hljs-string">"<span class="hljs-variable">$list</span>\serviceLog.log"</span> <span class="hljs-comment">#will create later</span>


<span class="hljs-variable">$logentries</span> = <span class="hljs-selector-tag">@</span>()
<span class="hljs-variable">$sessions</span> = <span class="hljs-selector-tag">@</span>()


<span class="hljs-variable">$servers</span> = <span class="hljs-built_in">Get-Content</span> <span class="hljs-literal">-Path</span> <span class="hljs-variable">$serverListPath</span>


<span class="hljs-keyword">foreach</span>(<span class="hljs-variable">$server</span> <span class="hljs-keyword">in</span> <span class="hljs-variable">$servers</span>){
    <span class="hljs-keyword">try</span> {
        <span class="hljs-variable">$session</span> = <span class="hljs-built_in">New-PSSession</span> <span class="hljs-literal">-ComputerName</span> <span class="hljs-variable">$server</span> <span class="hljs-literal">-ErrorAction</span> Stop
        <span class="hljs-variable">$sessions</span> += <span class="hljs-variable">$session</span>
    }
    <span class="hljs-keyword">catch</span>{
        <span class="hljs-variable">$logentry</span> = <span class="hljs-string">"Server: <span class="hljs-variable">$server</span> - Error to create session - <span class="hljs-variable">$_</span>"</span>
        <span class="hljs-variable">$logentry</span> += <span class="hljs-variable">$logentry</span>
        <span class="hljs-built_in">Write-Host</span> <span class="hljs-variable">$logentry</span>
    }

}

<span class="hljs-keyword">foreach</span>(<span class="hljs-variable">$session</span> <span class="hljs-keyword">in</span> <span class="hljs-variable">$sessions</span>){
    <span class="hljs-variable">$serverName</span> = <span class="hljs-variable">$session</span>.ComputerName
    <span class="hljs-keyword">try</span>{
        <span class="hljs-variable">$checkService</span> = <span class="hljs-built_in">Invoke-Command</span> <span class="hljs-literal">-Session</span> <span class="hljs-variable">$session</span> <span class="hljs-literal">-ScriptBlock</span> { <span class="hljs-built_in">Get-Service</span> <span class="hljs-literal">-Name</span> <span class="hljs-string">"ctxprofile"</span> <span class="hljs-literal">-ErrorAction</span> stop}  

        <span class="hljs-keyword">if</span>(<span class="hljs-variable">$checkService</span>.status <span class="hljs-operator">-eq</span> <span class="hljs-string">"running"</span>){
            <span class="hljs-variable">$logentry</span> = <span class="hljs-string">"server <span class="hljs-variable">$serverName</span>  - service ctxprofile is running"</span>
        }
        <span class="hljs-keyword">else</span> {<span class="hljs-variable">$logentry</span> = <span class="hljs-string">"server <span class="hljs-variable">$serverName</span>  - service ctxprofile is not running"</span> }

    }
    <span class="hljs-keyword">catch</span>{
        <span class="hljs-variable">$logentry</span> = <span class="hljs-string">"Service ctxProfile not found on server: <span class="hljs-variable">$server</span> -error: <span class="hljs-variable">$_</span>"</span>
    }

    <span class="hljs-variable">$logEntries</span> += <span class="hljs-variable">$logentry</span>
    <span class="hljs-built_in">Write-Host</span> <span class="hljs-variable">$logentry</span>
}

<span class="hljs-comment">#need to close all Pssessions</span>
<span class="hljs-comment">&lt;#$sessions | ForEach-Object {
    Remove-PSSession -Session $_
} #&gt;</span>

<span class="hljs-comment">#or can use like this</span>
<span class="hljs-keyword">foreach</span>(<span class="hljs-variable">$session</span> <span class="hljs-keyword">in</span> <span class="hljs-variable">$sessions</span>){
    <span class="hljs-built_in">Remove-PSSession</span> <span class="hljs-literal">-Session</span> <span class="hljs-variable">$session</span>
}


<span class="hljs-variable">$logentries</span> | <span class="hljs-built_in">Out-File</span> <span class="hljs-literal">-FilePath</span> <span class="hljs-variable">$logFilePath</span> <span class="hljs-literal">-Force</span>
<span class="hljs-variable">$logentries</span> | <span class="hljs-built_in">Out-GridView</span> <span class="hljs-literal">-Title</span> <span class="hljs-string">"Service Check Results"</span>
</code></pre>
]]></content:encoded></item><item><title><![CDATA[Step-by-Step Guide to Upgrade Citrix NetScaler Firmware]]></title><description><![CDATA[Objective: Update Citrix NetScaler to the latest version to address detected vulnerabilities. Due to low success rates when updating through the GUI, alternative methods will be explored.
Why Update NetScaler?
Updating NetScaler is crucial for severa...]]></description><link>https://ahmadafif.com/upgrade-citrix-netscaler-firmware</link><guid isPermaLink="true">https://ahmadafif.com/upgrade-citrix-netscaler-firmware</guid><category><![CDATA[netscaler]]></category><category><![CDATA[Citrix]]></category><dc:creator><![CDATA[Ahmad Afif]]></dc:creator><pubDate>Tue, 23 Jul 2024 03:37:26 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1721706654041/1c852ce6-3f0a-4efa-9118-b5efef6838dd.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>Objective:</strong> Update Citrix NetScaler to the latest version to address detected vulnerabilities. Due to low success rates when updating through the GUI, alternative methods will be explored.</p>
<h4 id="heading-why-update-netscaler">Why Update NetScaler?</h4>
<p>Updating NetScaler is crucial for several reasons:</p>
<ul>
<li><p><strong>Security:</strong> when the IT security guy chasing you for vulnerabilities</p>
</li>
<li><p><strong>Performance:</strong> Benefit from performance improvements in newer versions.</p>
</li>
<li><p><strong>Features:</strong> Access new features and enhancements.</p>
</li>
</ul>
<ol>
<li>Install WinSCP:</li>
</ol>
<ul>
<li>Ensure you have WinSCP installed on your machine. You can download it from <a target="_blank" href="https://winscp.net/eng/download.php">WinSCP official website.</a></li>
</ul>
<ol start="2">
<li>Login to NetScaler using WinSCP:</li>
</ol>
<ul>
<li><p>Open WinSCP.</p>
</li>
<li><p>Enter the IP address of your NetScaler, username, and password.</p>
</li>
<li><p>Click "Login".</p>
<ol start="3">
<li><p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1721704398041/2e283746-d5a6-41b3-a4e7-83e176c3c5c1.png" alt class="image--center mx-auto" /></p>
<p> Backup Configuration File:</p>
</li>
</ol>
</li>
</ul>
<ul>
<li><p>Navigate to the <code>/nsconfig</code> directory.</p>
</li>
<li><p>Backup the ns.conf file by copying it to your local machine.</p>
<p>  <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1721704853734/7184c13d-293e-4379-b030-2974e01a8fe8.png" alt class="image--center mx-auto" /></p>
</li>
</ul>
<ol start="4">
<li>Create Full Backup via Web Interface:</li>
</ol>
<ul>
<li><p>Log in to NetScaler using your web browser.</p>
</li>
<li><p>Navigate to  <strong>Configuration &gt; System &gt; Backup and Restore</strong>.</p>
</li>
<li><p>For naming format: File name [DD-MM-YY], select full in level option</p>
</li>
</ul>
<p>    <img alt /></p>
<ul>
<li><p>Create a full backup and download it to your local machine.<br />  Create Snapshot via vCenter:</p>
</li>
<li><p>Log in to vCenter.</p>
</li>
<li><p>Locate your NetScaler virtual machine.</p>
</li>
<li><p>Create a snapshot for the NetScaler.</p>
</li>
</ul>
<p>    <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1721704463914/3ccfedd4-4d84-4503-b5fb-7d1ebeb33f1d.png" alt class="image--center mx-auto" /></p>
<ol start="6">
<li>Upload Firmware using WinSCP:</li>
</ol>
<ul>
<li><p>In WinSCP, navigate to the <code>/var/nsinstall</code> directory.</p>
<p>  <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1721705100623/46ac0e5c-f6dd-44a0-83ff-545414ae0f24.png" alt class="image--center mx-auto" /></p>
</li>
<li><p>Create a new folder named after the firmware version you are upgrading to.</p>
</li>
<li><p>Upload the firmware file to this newly created folder.</p>
</li>
</ul>
<ol start="7">
<li>Run PuTTY:</li>
</ol>
<ul>
<li><p>Open PuTTY.</p>
<p>  <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1721704518269/b23c696e-57c7-4730-90a1-f512c2698169.png" alt class="image--center mx-auto" /></p>
</li>
<li><p>Enter the IP address of your NetScaler and connect.</p>
</li>
</ul>
<p>    <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1721704552861/78d20b74-2932-45cf-b53e-b62d864270b5.png" alt class="image--center mx-auto" /></p>
<ol start="8">
<li>Access Shell:</li>
</ol>
<ul>
<li>Once connected, type shell to access the shell prompt.</li>
</ul>
<ol start="9">
<li>Check Disk Space:</li>
</ol>
<ul>
<li><p>Type <code>df -h</code> to view the available disk space.</p>
</li>
<li><p>Ensure the /var partition has at least 4GB of free space.</p>
</li>
</ul>
<p>    <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1721704643638/b74b0e70-9f89-4d0d-ae62-45d11fcb09b7.png" alt class="image--center mx-auto" /></p>
<ol start="10">
<li>Navigate to Firmware Directory:</li>
</ol>
<ul>
<li><p>Type <code>cd /var/nsinstall/&lt;firmware_folder&gt;</code> to navigate to the firmware directory.</p>
</li>
<li><p>Type ls to see all the list that available</p>
</li>
</ul>
<ol start="11">
<li>Extract Firmware:</li>
</ol>
<ul>
<li><p>Type <code>tar -xvzf &lt;firmware_name.tgz&gt;</code> to extract the firmware files.</p>
</li>
<li><p>Type <code>ls -tl</code> once the extraction completed to see list is available</p>
<p>  <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1721704693765/b51e2a89-61f6-4020-817c-1f036f843da0.png" alt class="image--center mx-auto" /></p>
</li>
</ul>
<ol start="12">
<li>Install Firmware:</li>
</ol>
<ul>
<li><p>Type installns to start the firmware installation process.</p>
<p>  <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1721704738705/84a7cc03-5f76-4e5e-8c0a-b8b1a96bfd16.png" alt class="image--center mx-auto" /></p>
</li>
<li><p>Respond to any confirmation prompts as needed. Delete old signature files and kernel images if asked</p>
</li>
</ul>
<ol start="13">
<li>Wait for Reboot:</li>
</ol>
<ul>
<li><p>The NetScaler will automatically reboot once the firmware installation is complete.</p>
</li>
<li><p>Can monitor the process at the vcenter</p>
</li>
</ul>
<ol start="14">
<li>Verify Firmware Version:</li>
</ol>
<ul>
<li><p>After the reboot, log in to the NetScaler via the web interface.</p>
</li>
<li><p>Make sure it does not change to freemium</p>
</li>
<li><p>Check the firmware version by clicking on the <strong>profile</strong> account in the top right corner.</p>
</li>
<li><p>Can also try validation by launching the storefront or SSH to netscaler and type <code>show version</code></p>
<p>  <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1721704777690/56102223-8132-4dae-874e-7772a65b5e98.png" alt class="image--center mx-auto" /></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[How to Build a YouTube Video Downloader in Python: A Beginner's Guide]]></title><description><![CDATA[Usecase:
Online websites for downloading YouTube videos are often full of ads and complicated to use. For educational purposes, here’s a simple, ad-free way to create your own YouTube video downloader using Python.
Result:


Lets get started!
Let me ...]]></description><link>https://ahmadafif.com/how-to-build-a-youtube-video-downloader-in-python-a-beginners-guide</link><guid isPermaLink="true">https://ahmadafif.com/how-to-build-a-youtube-video-downloader-in-python-a-beginners-guide</guid><category><![CDATA[2Articles1Week]]></category><category><![CDATA[Python]]></category><category><![CDATA[pytube]]></category><dc:creator><![CDATA[Ahmad Afif]]></dc:creator><pubDate>Fri, 24 May 2024 05:13:54 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/8dOk8JVESxY/upload/039174395d9873b7ddcda0e4892b2da5.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h3 id="heading-usecase"><strong>Usecase:</strong></h3>
<p>Online websites for downloading YouTube videos are often full of ads and complicated to use. For educational purposes, here’s a simple, ad-free way to create your own YouTube video downloader using Python.</p>
<h3 id="heading-result">Result:</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1716523996982/8ba3506c-ddb1-44dc-9643-0dc2cbba418d.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1716524040498/779c981d-ec5a-47f9-ab3a-9e7088625bd0.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-lets-get-started">Lets get started!</h3>
<p>Let me show you the essential script.</p>
<pre><code class="lang-python"><span class="hljs-comment">#pip install pytube (if module not installed yet)</span>
<span class="hljs-keyword">from</span> pytube <span class="hljs-keyword">import</span> YouTube

<span class="hljs-comment">#insert the link you want to download</span>
yt = YouTube(<span class="hljs-string">"https://www.youtube.com/watch?v=K9tOnIJyvRw"</span>)
<span class="hljs-comment">#streams object able to use multiple method to call</span>
yt.streams.get_highest_resolution().download()
print(<span class="hljs-string">f"Video downloaded successfully: '<span class="hljs-subst">{yt.title}</span>'"</span>)
</code></pre>
<p>That's all you need.</p>
<ul>
<li><p><code>yt.streams</code>: This gets all available video streams for the YouTube video.</p>
</li>
<li><p><code>get_highest_resolution()</code>: This method selects the stream with the highest resolution.</p>
</li>
<li><p><code>download()</code>: This method downloads the selected video stream.</p>
</li>
</ul>
<p>From the test, it seems that unable to call method directly but need to call method from object <code>streams</code> which is <code>get_highest_resolution()</code></p>
<p>Full explanation:</p>
<p>You cannot call the <code>download</code> method directly on the <code>YouTube</code> object because the <code>download</code> method is a part of the <code>Stream</code> object, not the <code>YouTube</code> object. Here’s a more detailed explanation:</p>
<ol>
<li><p><code>YouTube</code> <strong>Object</strong>:</p>
<ul>
<li>When you create a <code>YouTube</code> object, it represents a specific YouTube video. This object contains various attributes and methods to interact with the video.</li>
</ul>
</li>
<li><p><code>Stream</code> <strong>Object</strong>:</p>
<ul>
<li>The <code>YouTube</code> object has a <code>streams</code> attribute, which is a list of <code>Stream</code> objects. Each <code>Stream</code> object represents a different format or quality option for downloading the video (e.g., 360p, 720p, audio-only, etc.).</li>
</ul>
</li>
<li><p><code>download</code> <strong>Method</strong>:</p>
<ul>
<li>The <code>download</code> method is a method of the <code>Stream</code> class, not the <code>YouTube</code> class. Therefore, you need to first select a specific <code>Stream</code> object from the <code>streams</code> attribute of the <code>YouTube</code> object before you can call the <code>download</code> method.</li>
</ul>
</li>
</ol>
<p>We want to make it better by stating the path to save downloaded video and also URL for video.</p>
<pre><code class="lang-python"><span class="hljs-comment">#pip install pytube (if module not installed yet)</span>
<span class="hljs-keyword">from</span> pytube <span class="hljs-keyword">import</span> YouTube
</code></pre>
<p>Importing Youtube class from pytube library which will be used to interacting with Youtube videos</p>
<pre><code class="lang-python"><span class="hljs-comment"># Create function to download a YouTube video</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">download_video</span>(<span class="hljs-params">video_url, output_path</span>):</span>
    <span class="hljs-keyword">try</span>:
        video = YouTube(video_url)
        stream = video.streams.get_highest_resolution()
        stream.download(output_path=output_path)
        print(<span class="hljs-string">f"Video downloaded successfully: '<span class="hljs-subst">{video.title}</span>'"</span>)
    <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> e:
        print(<span class="hljs-string">f"Failed to download video. Error: <span class="hljs-subst">{e}</span>"</span>)
</code></pre>
<p><code>def download_video(video_url, output_path)</code> is defining a function name as download video that takes two arguments which is video url and output path</p>
<ul>
<li><p><code>video_url</code>: The URL of the YouTube video to be downloaded.</p>
</li>
<li><p><code>output_path</code>: The directory where the downloaded video will be saved.</p>
</li>
</ul>
<p><code>video = YouTube(video_url)</code> create 'Youtube' object for specified video URL that will called later as variable video</p>
<p><code>stream = video.streams.get_highest_resolution()</code> Selects the highest resolution stream available for the video.</p>
<p><code>stream.download(output_path=output_path)</code> Downloads the selected stream to the specified directory.</p>
<p><code>print(f"Video downloaded successfully: '{video.title}'")</code> output if the video downloaded successfully</p>
<p><code>except Exception as e: print(f"Failed to download video. Error: {e}")</code> when the download not able to process, this output will trigger</p>
<pre><code class="lang-python">video_url = input(<span class="hljs-string">'Enter URL of YouTube video: '</span>) 
output_path = input(<span class="hljs-string">'Enter the directory to save the video: '</span>)
</code></pre>
<p>input means that user will need to fill the URL and also the path which will be stored to the variable video_url and output_path</p>
<pre><code class="lang-python">download_video(video_url, output_path)
</code></pre>
<p>This last script will call <code>download_video</code> function with the argument that has been given by user.</p>
<h3 id="heading-full-code"><strong>FULL CODE</strong></h3>
<pre><code class="lang-python"><span class="hljs-comment"># pip install pytube (if module not installed yet)</span>
<span class="hljs-keyword">from</span> pytube <span class="hljs-keyword">import</span> YouTube

<span class="hljs-comment"># Function to download a YouTube video</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">download_video</span>(<span class="hljs-params">video_url, output_path</span>):</span>
    <span class="hljs-keyword">try</span>:
        video = YouTube(video_url)
        stream = video.streams.get_highest_resolution()
        stream.download(output_path=output_path)
        print(<span class="hljs-string">f"Video downloaded successfully: '<span class="hljs-subst">{video.title}</span>'"</span>)
    <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> e:
        print(<span class="hljs-string">f"Failed to download video. Error: <span class="hljs-subst">{e}</span>"</span>)

<span class="hljs-comment"># Request URL of YouTube Video</span>
video_url = input(<span class="hljs-string">'Enter URL of YouTube video: '</span>)
output_path = input(<span class="hljs-string">'Enter the directory to save the video: '</span>)

<span class="hljs-comment"># Download the video</span>
download_video(video_url, output_path)
</code></pre>
<p>Let me know if you have any comment.</p>
]]></content:encoded></item><item><title><![CDATA[PowerShell 101: Instantly Copy Files and Auto-Install Applications on Multiple Servers]]></title><description><![CDATA[Use Case:
Managing multiple servers often involves repetitive tasks, such as installing specific software. Manual installation on each server can be time-consuming and prone to errors. I will demonstrate how to use PowerShell to automate the installa...]]></description><link>https://ahmadafif.com/powershell-copy-files-install-apps-multiple-servers</link><guid isPermaLink="true">https://ahmadafif.com/powershell-copy-files-install-apps-multiple-servers</guid><category><![CDATA[Powershell]]></category><category><![CDATA[Scripting]]></category><dc:creator><![CDATA[Ahmad Afif]]></dc:creator><pubDate>Thu, 23 May 2024 04:56:42 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/lVZjvw-u9V8/upload/ef925682012cb7aefd53d304e4853125.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h3 id="heading-use-case">Use Case:</h3>
<p>Managing multiple servers often involves repetitive tasks, such as installing specific software. Manual installation on each server can be time-consuming and prone to errors. I will demonstrate how to use PowerShell to automate the installation.</p>
<p>My sample application is Notepad++ which will be installed to several servers in my lab environment.</p>
<p>You can download the Notepad++ installer from the <a target="_blank" href="https://notepad-plus-plus.org/">official website.</a></p>
<h3 id="heading-solution">Solution:</h3>
<p>Will use PowerShell script with few functions. In addition, will create log file for reference and troubleshoot purpose.</p>
<h3 id="heading-result">Result:</h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1716435378969/e0c84d85-edf5-4ffc-9664-9821b8f6ef09.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1716435642294/cc06dfa4-2191-4016-8d7a-56fb001435f6.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1716440043207/96c3aea0-5724-47d0-ac42-36b44c05fede.png" alt class="image--center mx-auto" /></p>
<p>Using the PowerShell script, Notepad++ will be installed on multiple servers, and a log file will be created. This eliminates the need for manual installation on each server. Full code at the end of article.</p>
<h3 id="heading-lets-get-started"><strong>Lets get started!</strong></h3>
<h3 id="heading-pre-requisite">Pre-requisite</h3>
<ol>
<li><p>Ensure each server has access to the network location where the Notepad++ installer is stored.</p>
</li>
<li><p>Ensure PowerShell Remoting is enabled on all servers.</p>
</li>
<li><p>Ensure the user running the script has administrative privileges on all target servers.</p>
</li>
</ol>
<p>In production environment, you need to make sure network and firewall settings allow for PowerShell Remoting and access to the network share.</p>
<pre><code class="lang-powershell"><span class="hljs-variable">$installerLocalPath</span> = <span class="hljs-string">"C:\CopyFiles\npp.8.6.7.Installer.x64.exe"</span>
<span class="hljs-variable">$installerRemotePath</span> = <span class="hljs-string">"C:\Program Files\npp.8.6.7.Installer.x64.exe"</span>
<span class="hljs-variable">$serverList</span> = <span class="hljs-string">"C:\Copyfiles\server.txt"</span>
<span class="hljs-variable">$installerArguments</span> = <span class="hljs-string">"/S"</span>  <span class="hljs-comment"># Silent install argument for Notepad++</span>
<span class="hljs-variable">$servers</span> = <span class="hljs-built_in">Get-Content</span> <span class="hljs-literal">-Path</span> <span class="hljs-variable">$serverList</span>
<span class="hljs-variable">$logFilePath</span> = <span class="hljs-string">"C:\CopyFiles\install_log.txt"</span>
</code></pre>
<p><code>$installerLocalPath</code> the variable for Notepad++ installer as source file</p>
<p><code>$installerRemotePath</code> Destination where the installer will be copied</p>
<p><code>$serverList</code> is text file that contain list of server. For test purpose I just use two servers in Lab Environment as below. Can use IP address but of course not convenient</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1716436430171/f76958bc-617c-44a7-8309-955fc15b788c.png" alt class="image--center mx-auto" /></p>
<p><code>$installerArguments</code> is set to <code>/S</code> which the installer will run in silent mode without user interaction. There are more other option like:</p>
<p><code>/D=&lt;path&gt;</code> to specify installation directory</p>
<p><code>/noDesktopShortcut</code> will not create desktop shortcut</p>
<p><code>/noUpdater</code> prevent automatic updates especially in controlled environment.</p>
<p>Best practice is specify installation directory and silent installation <code>/S</code> to avoid manual intervention</p>
<p><code>$servers = Get-Content -Path $serverList</code> Reads the content for specified file in $serverList variable. Each line inside the file represent different server</p>
<pre><code class="lang-powershell"><span class="hljs-built_in">Start-Transcript</span> <span class="hljs-literal">-Path</span> <span class="hljs-variable">$logFilePath</span> <span class="hljs-literal">-Append</span>
</code></pre>
<p>Start a transcript which will log all output from the script. It will append to the file if it already exist.</p>
<pre><code class="lang-powershell"><span class="hljs-keyword">foreach</span> (<span class="hljs-variable">$server</span> <span class="hljs-keyword">in</span> <span class="hljs-variable">$servers</span>) {

    <span class="hljs-keyword">try</span> {
        <span class="hljs-variable">$session</span> = <span class="hljs-built_in">New-PSSession</span> <span class="hljs-literal">-ComputerName</span> <span class="hljs-variable">$server</span>
        <span class="hljs-built_in">Copy-Item</span> <span class="hljs-literal">-Path</span> <span class="hljs-variable">$sourcepath</span> <span class="hljs-literal">-Destination</span> <span class="hljs-variable">$destinationPath</span> <span class="hljs-literal">-ToSession</span> <span class="hljs-variable">$session</span> <span class="hljs-literal">-Force</span>
        <span class="hljs-built_in">Write-Host</span> <span class="hljs-string">"Copy to <span class="hljs-variable">$server</span> successful"</span>
</code></pre>
<p><code>foreach ($server in $servers)</code> will create a loop based on server.</p>
<p>I'm using <code>try</code> to handle any error during execution of the command will pass information.</p>
<p><code>$session = New-PSSession -ComputerName $server</code> will create PowerShell session to server specified which is variable $server</p>
<p><code>Copy-Item -Path $sourcepath -Destination $destinationPath -ToSession $session -Force</code> :It will copy installer from <code>$sourcePath</code> and paste it to <code>$destinationPath</code>.</p>
<p><code>-ToSession $session</code> is needed to specify to which remote session it will be copied. If not, it will copy the file locally and not into the remote servers</p>
<p><code>-force</code> will overwrite if there is existing file</p>
<pre><code class="lang-powershell">        <span class="hljs-comment"># Command to install the software</span>
        <span class="hljs-variable">$installCommand</span> = {
            <span class="hljs-keyword">param</span>(<span class="hljs-variable">$installerPath</span>, <span class="hljs-variable">$installerArguments</span>)
            <span class="hljs-built_in">Start-Process</span> <span class="hljs-literal">-FilePath</span> <span class="hljs-variable">$installerPath</span> <span class="hljs-literal">-ArgumentList</span> <span class="hljs-variable">$installerArguments</span> <span class="hljs-literal">-Wait</span>}
</code></pre>
<p><code>param($installerPath, $installerArguments)</code> there is two parameters. The installer path and also installer arguments. As we declare before installer path and <code>$installerArguments</code> we already set as <code>/s</code> which will be silent installation.</p>
<p><code>Start-Process -FilePath $installerPath -ArgumentList $installerArguments -Wait</code></p>
<p><code>Start-Process</code> will start new process</p>
<p><code>-Wait</code> will ask PowerShell to wait for the process to complete before proceeding to the next command. This ensures that the installation finishes before moving on.</p>
<p>Later, it will be use <code>Invoke-command</code> to run all parameters</p>
<pre><code class="lang-powershell"><span class="hljs-built_in">Invoke-Command</span> <span class="hljs-literal">-Session</span> <span class="hljs-variable">$session</span> <span class="hljs-literal">-ScriptBlock</span> <span class="hljs-variable">$installCommand</span> <span class="hljs-literal">-ArgumentList</span> <span class="hljs-variable">$softwareInstallerPath</span>, <span class="hljs-variable">$installerArguments</span>
        <span class="hljs-built_in">Write-Host</span> <span class="hljs-string">"Software installation on <span class="hljs-variable">$server</span> successful"</span>
</code></pre>
<p><code>Invoke-Command</code> will run a command or script block on remote computers</p>
<p>It passes the installer path and arguments to the script block.</p>
<p>If success, output "Software installation on $server successful"</p>
<pre><code class="lang-powershell"><span class="hljs-keyword">catch</span> { <span class="hljs-built_in">Write-Host</span> <span class="hljs-string">"Error during operation on {<span class="hljs-variable">$server</span>}: <span class="hljs-variable">$_</span>"</span>}
</code></pre>
<p>Any errors that occur during the try block and outputs an error message to the console, including the server name and the error details</p>
<pre><code class="lang-powershell">} <span class="hljs-keyword">finally</span> { <span class="hljs-built_in">Remove-PSSession</span> <span class="hljs-literal">-Session</span> <span class="hljs-variable">$session</span> <span class="hljs-built_in">Write-Host</span> <span class="hljs-string">"Session ended for <span class="hljs-variable">$server</span>"</span> }
</code></pre>
<p>Ensures that the PowerShell session to the server is removed. This is best practice to make sure after work completed, remove remote session.</p>
<pre><code class="lang-powershell"><span class="hljs-built_in">Stop-Transcript</span>
</code></pre>
<p>Stop the log. and it will generate the txt file.</p>
<h3 id="heading-full-script"><strong>FULL SCRIPT</strong></h3>
<pre><code class="lang-powershell"><span class="hljs-variable">$installerLocalPath</span> = <span class="hljs-string">"C:\CopyFiles\npp.8.6.7.Installer.x64.exe"</span>
<span class="hljs-variable">$installerRemotePath</span> = <span class="hljs-string">"C:\Program Files\npp.8.6.7.Installer.x64.exe"</span>
<span class="hljs-variable">$serverList</span> = <span class="hljs-string">"C:\Copyfiles\server.txt"</span>
<span class="hljs-variable">$installerArguments</span> = <span class="hljs-string">"/S"</span>  <span class="hljs-comment"># Silent install argument for Notepad++</span>
<span class="hljs-variable">$servers</span> = <span class="hljs-built_in">Get-Content</span> <span class="hljs-literal">-Path</span> <span class="hljs-variable">$serverList</span>
<span class="hljs-variable">$logFilePath</span> = <span class="hljs-string">"C:\CopyFiles\install_log.txt"</span>

<span class="hljs-built_in">Start-Transcript</span> <span class="hljs-literal">-Path</span> <span class="hljs-variable">$logFilePath</span> <span class="hljs-literal">-Append</span>

<span class="hljs-keyword">foreach</span> (<span class="hljs-variable">$server</span> <span class="hljs-keyword">in</span> <span class="hljs-variable">$servers</span>) {
    <span class="hljs-keyword">try</span> {
        <span class="hljs-variable">$session</span> = <span class="hljs-built_in">New-PSSession</span> <span class="hljs-literal">-ComputerName</span> <span class="hljs-variable">$server</span>

        <span class="hljs-comment"># Copy the installer to the remote server</span>
        <span class="hljs-built_in">Copy-Item</span> <span class="hljs-literal">-Path</span> <span class="hljs-variable">$installerLocalPath</span> <span class="hljs-literal">-Destination</span> <span class="hljs-variable">$installerRemotePath</span> <span class="hljs-literal">-ToSession</span> <span class="hljs-variable">$session</span> <span class="hljs-literal">-Force</span>
        <span class="hljs-built_in">Write-Host</span> <span class="hljs-string">"Installer copy to <span class="hljs-variable">$server</span> successful"</span>

        <span class="hljs-comment"># Command to install the software</span>
        <span class="hljs-variable">$installCommand</span> = {
            <span class="hljs-keyword">param</span>(<span class="hljs-variable">$installerPath</span>, <span class="hljs-variable">$installerArguments</span>)
            <span class="hljs-built_in">Start-Process</span> <span class="hljs-literal">-FilePath</span> <span class="hljs-variable">$installerPath</span> <span class="hljs-literal">-ArgumentList</span> <span class="hljs-variable">$installerArguments</span> <span class="hljs-literal">-Wait</span>
        }

        <span class="hljs-built_in">Invoke-Command</span> <span class="hljs-literal">-Session</span> <span class="hljs-variable">$session</span> <span class="hljs-literal">-ScriptBlock</span> <span class="hljs-variable">$installCommand</span> <span class="hljs-literal">-ArgumentList</span> <span class="hljs-variable">$installerRemotePath</span>, <span class="hljs-variable">$installerArguments</span>
        <span class="hljs-built_in">Write-Host</span> <span class="hljs-string">"Software installation on <span class="hljs-variable">$server</span> successful"</span>
    }
    <span class="hljs-keyword">catch</span> {
        <span class="hljs-built_in">Write-Host</span> <span class="hljs-string">"Error during operation on {<span class="hljs-variable">$server</span>}: <span class="hljs-variable">$_</span>"</span>
    }
    <span class="hljs-keyword">finally</span> {
        <span class="hljs-built_in">Remove-PSSession</span> <span class="hljs-literal">-Session</span> <span class="hljs-variable">$session</span>
        <span class="hljs-built_in">Write-Host</span> <span class="hljs-string">"Session ended for <span class="hljs-variable">$server</span>"</span>
    }
}

<span class="hljs-built_in">Stop-Transcript</span>
</code></pre>
<h3 id="heading-improvement">Improvement</h3>
<ol>
<li><p>Can have unit test to validate functionality</p>
</li>
<li><p>GUI version where can be user-friendly like can browse the resource file etc.</p>
</li>
</ol>
<p>Let me know if you have any comment.</p>
]]></content:encoded></item><item><title><![CDATA[Automate Web Data Extraction 101: Scraping 2400+ Entries with Python]]></title><description><![CDATA[Use case:
I helped a friend extract a list of Matta Members (https://www.matta.org.my/members) into an Excel files. With over 2400 entries across 49 web pages, manually copying each entry is impractical and waste of time.

Solution:
Using Python’s Be...]]></description><link>https://ahmadafif.com/web-scraping-extract-2400-entries</link><guid isPermaLink="true">https://ahmadafif.com/web-scraping-extract-2400-entries</guid><category><![CDATA[Python]]></category><category><![CDATA[web scraping]]></category><category><![CDATA[BeautifulSoup]]></category><dc:creator><![CDATA[Ahmad Afif]]></dc:creator><pubDate>Tue, 21 May 2024 08:02:40 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/XJXWbfSo2f0/upload/420ec7d5fd2b6ca4ad8eff02160fbef7.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h3 id="heading-use-case"><strong>Use case:</strong></h3>
<p>I helped a friend extract a list of Matta Members (<a target="_blank" href="https://www.matta.org.my/members">https://www.matta.org.my/members</a>) into an Excel files. With over 2400 entries across 49 web pages, manually copying each entry is impractical and waste of time.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1716272186389/5f8650a1-6801-43f5-999f-3373ab26687a.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-solution"><strong>Solution:</strong></h3>
<p>Using Python’s BeautifulSoup and Requests modules, I automated the data extraction. If the site required credentials or used JavaScript, I would have needed Selenium or a similar tool.</p>
<h3 id="heading-result"><strong>Result:</strong></h3>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1716279593824/75547a9c-bb17-48d2-9657-b2f76ac7ee21.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-lets-get-started">Let's get started!</h3>
<p>First, we need to know what modules to use.</p>
<h3 id="heading-python-modules">Python Modules:</h3>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> requests
<span class="hljs-keyword">from</span> bs4 <span class="hljs-keyword">import</span> BeautifulSoup
<span class="hljs-keyword">import</span> pandas <span class="hljs-keyword">as</span> pd
<span class="hljs-keyword">import</span> os
</code></pre>
<p><strong>BeautifulSoup</strong>: Mainly use to extract data from HTML content on webpages. If extract from local, Requests module is enough</p>
<p><strong>How BeautifulSoup work?</strong></p>
<ol>
<li><p><strong>Loading HTML/XML:</strong> The first step is loading the HTML content into BeautifulSoup.</p>
</li>
<li><p><strong>Creating the Soup Object:</strong> BeautifulSoup parses the content and creates a "soup" object, which represents the document.</p>
</li>
<li><p><strong>Navigating and Searching:</strong> The soup object allows you to navigate and search through the parsed document easily, using methods like <code>find()</code>, <code>find_all()</code>, <code>select()</code>, etc.</p>
</li>
</ol>
<p><strong>Requests</strong>: Make HTTP requests in Python. It will fetch HTML content</p>
<p><strong>Pandas</strong>: The library provide data analysis tools. Will use to store and manipulate extracted data in tabular as example below:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1716271361235/6290ddb8-5a49-4026-9f09-fd66b6d13ca6.png" alt="example of Panda module" class="image--center mx-auto" /></p>
<p><strong>OS</strong>: The module use to interact with operating system. Will use to handle file path once finish extract and create into csv file</p>
<h3 id="heading-defining-the-function">Defining the function</h3>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">scrape_matta_members</span>():</span>
    base_url = <span class="hljs-string">"https://www.matta.org.my/members?page="</span>
    members = []
</code></pre>
<p><code>def scrape_matta_members()</code>: defines function as per name stated</p>
<p><code>base_url</code> is the web address for the page that we want to capture. Normally main website is till '?' symbol. After that its on the search or other details.</p>
<p><code>members = [ ]</code>: It is for initialize empty list to store member details later</p>
<h3 id="heading-looping">Looping</h3>
<pre><code class="lang-python">    <span class="hljs-keyword">for</span> page <span class="hljs-keyword">in</span> range(<span class="hljs-number">1</span>, <span class="hljs-number">50</span>):
        url = <span class="hljs-string">f"<span class="hljs-subst">{base_url}</span><span class="hljs-subst">{page}</span>"</span>
        response = requests.get(url)
        <span class="hljs-keyword">if</span> response.status_code != <span class="hljs-number">200</span>:
            print(<span class="hljs-string">f"Failed to load page <span class="hljs-subst">{page}</span>, status code: <span class="hljs-subst">{response.status_code}</span>"</span>)
            <span class="hljs-keyword">continue</span>
</code></pre>
<p><code>for page in range(1, 50)</code>: Will loop from page 1 till 49.</p>
<p><code>url = f"{base_url}{page}"</code>: Constructs the URL for each page by appending the page number to the base URL. Page is int</p>
<p><code>response = requests.get(url)</code>: will send HTTP GET request to the URL that we declare.</p>
<p><code>if response.status_code != 200</code>: Checks if the request was successful (status code 200 means ok which request succesfully received, understood and accepted)</p>
<p><code>print(f"Failed to load page {page}, status code: {response.status_code}")</code>: Will print this if unable to capture from the URL</p>
<p><code>continue</code>: Skips to the next iteration of the loop if the request failed.</p>
<h3 id="heading-parse">Parse</h3>
<pre><code class="lang-python"> soup = BeautifulSoup(response.content, <span class="hljs-string">'html.parser'</span>)

        <span class="hljs-comment"># Find the section containing the member details</span>
        member_list = soup.find_all(<span class="hljs-string">'div'</span>, class_=<span class="hljs-string">'card-box'</span>)
        <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> member_list:
            print(<span class="hljs-string">f"No members found on page <span class="hljs-subst">{page}</span>"</span>)
            <span class="hljs-keyword">continue</span>
</code></pre>
<p><code>soup = BeautifulSoup(response.content, 'html.parser')</code>: Parses the HTML content of the page using BeautifulSoup.</p>
<p>Parses means that it analyze and breakdown the HTML document into structure that easy to navigate. It is converting a string of HTML or XML code into a tree of Python objects</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1716274302569/5ec0f7a4-875f-46f3-a3dc-de84c7c846df.png" alt class="image--center mx-auto" /></p>
<p>For <code>member_list = soup.find_all('div', class_='card-box')</code> , you need to inspect the website to find the right class.</p>
<p>For this time, all of the details in inside <code>&lt;div class ="card-box"&gt;</code> , hence we will use this to find all <code>div</code> elements that contain all member details.</p>
<p><code>if not member_list</code>: Checks if no member elements were found.</p>
<p><code>print(f"No members found on page {page}")</code>: Prints a message if no members were found.</p>
<p><code>continue</code>: Skips to the next iteration of the loop if no members were found.</p>
<h3 id="heading-extract-and-store-details">Extract and Store Details</h3>
<pre><code class="lang-python">        <span class="hljs-keyword">for</span> member <span class="hljs-keyword">in</span> member_list:
            name = member.find(<span class="hljs-string">'a'</span>, class_=<span class="hljs-string">'search-title'</span>).get_text(strip=<span class="hljs-literal">True</span>)
            reg_number = member.find(<span class="hljs-string">'span'</span>, class_=<span class="hljs-string">'reg-number'</span>).get_text(strip=<span class="hljs-literal">True</span>)
            contact_number = member.find(<span class="hljs-string">'span'</span>, class_=<span class="hljs-string">'contact-number'</span>).get_text(strip=<span class="hljs-literal">True</span>)
            web_address = member.find(<span class="hljs-string">'span'</span>, class_=<span class="hljs-string">'web-address'</span>).get_text(strip=<span class="hljs-literal">True</span>)
            location = member.find(<span class="hljs-string">'span'</span>, class_=<span class="hljs-string">'location'</span>).get_text(separator=<span class="hljs-string">", "</span>, strip=<span class="hljs-literal">True</span>)

            members.append({
                <span class="hljs-string">'name'</span>: name,
                <span class="hljs-string">'reg_number'</span>: reg_number,
                <span class="hljs-string">'contact_number'</span>: contact_number,
                <span class="hljs-string">'web_address'</span>: web_address,
                <span class="hljs-string">'location'</span>: location
            })
        print(<span class="hljs-string">f"Page <span class="hljs-subst">{page}</span> scraped successfully."</span>)
</code></pre>
<p>Iterates over each member element in <code>member_list</code> and extracts the details.</p>
<p><code>name = member.find('a', class_='search-title').get_text(strip=True)</code>: Extracts the member's name.</p>
<p><code>reg_number = member.find('span', class_='reg-number').get_text(strip=True)</code>: Extracts the registration number.</p>
<p><code>contact_number = member.find('span', class_='contact-number').get_text(strip=True)</code>: Extracts the contact number.</p>
<p><code>web_address = member.find('span', class_='web-address').get_text(strip=True)</code>: Extracts the web address.</p>
<p><code>location = member.find('span', class_='location').get_text(separator=", ", strip=True)</code>: Extracts the location.</p>
<p>Appends the extracted details as a dictionary to the <code>members</code> list.</p>
<h3 id="heading-convert-the-extracted-data-to-dataframe">Convert the extracted data to DataFrame:</h3>
<pre><code class="lang-python"><span class="hljs-comment"># Run the function and get the dataframe</span>
df = scrape_matta_members()
</code></pre>
<p><code>df = pd.DataFrame(members)</code>: Converts the list of dictionaries to a Pandas DataFrame.</p>
<p><code>return df</code>: Returns the DataFrame.</p>
<h3 id="heading-defining-the-save-function">Defining the Save Function</h3>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">save_to_csv</span>(<span class="hljs-params">df, filename</span>):</span>
    df.to_csv(filename, index=<span class="hljs-literal">False</span>)
    full_path = os.path.abspath(filename)
    print(<span class="hljs-string">f"Data successfully saved to <span class="hljs-subst">{full_path}</span>"</span>)
</code></pre>
<p><code>def save_to_csv(df, filename)</code> defines the function as per mentioned name and takes DataFrame 'df' and filename as argument</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1716277538755/352e4a07-827d-4772-9a9b-a3bcdf44da6e.png" alt class="image--center mx-auto" /></p>
<p><a target="_blank" href="http://df.to"><code>df.to</code></a><code>_csv(filename, index=False)</code> saves the DataFrame to a CSV file with the specified filename, without including the DataFrame index.</p>
<p>if I put <code>index = True</code>, my csv will look like this:</p>
<pre><code class="lang-excel">,<span class="hljs-built_in">index</span>,name,reg_number,contact_number,web_address,location
<span class="hljs-number">0</span>,A'Famosa Travel &amp; Tours Sdn. Bhd.,<span class="hljs-number">456717</span>-K | <span class="hljs-symbol">MA1999</span>,+<span class="hljs-number">60</span> (<span class="hljs-number">06</span>) <span class="hljs-number">5520288</span>,www.afamosa.com,<span class="hljs-string">"Club House Building,A'Famosa Resort, Jalan Kemus, Simpang Empat, Alor Gajah, 78000, Melaka, Malaysia"</span>
<span class="hljs-number">1</span>,Afbatni Travel &amp; Services Sdn Bhd,<span class="hljs-number">1376398</span>-M | <span class="hljs-symbol">MA6643</span>,+<span class="hljs-number">60</span> (<span class="hljs-number">03332</span>) <span class="hljs-number">33272</span>,-,<span class="hljs-string">"No. 36A, Tingkat 1, Lorong Bayu Tinggi 4c, Taman Bayu Tinggi, Klang, 41200, Selangor, Malaysia"</span>
</code></pre>
<p><code>full_path = os.path.abspath(filename)</code> takes relative path and convert it to absolute path. <code>full_path</code> is variable that will contain the absolute path of file.</p>
<p><code>print(f"Data successfully saved to {full_path}")</code> is success message</p>
<h3 id="heading-scrape-data">Scrape Data</h3>
<pre><code class="lang-python"><span class="hljs-comment"># Run the function and get the dataframe</span>
df = scrape_matta_members()
</code></pre>
<p><code>df = scrape_matta_members()</code>: Calls the <code>scrape_matta_members</code> function as mentioned earlier and stores the returned DataFrame in the variable <code>df</code>.</p>
<h3 id="heading-save-data-to-csv">Save Data to CSV</h3>
<pre><code class="lang-python"><span class="hljs-comment"># Save the dataframe to a CSV file</span>
csv_filename = <span class="hljs-string">'matta_members.csv'</span>
save_to_csv(df, csv_filename)
</code></pre>
<ul>
<li><p><code>csv_filename = 'matta_members.csv'</code>: to give naming for the CSV file.</p>
</li>
<li><p><code>save_to_csv(df, csv_filename)</code>: Calls the <code>save_to_csv</code> function as defined before to save the DataFrame to the specified CSV file.</p>
</li>
</ul>
<p>Thats all. Comment me if need any other help</p>
<h3 id="heading-full-code"><strong>full code:</strong></h3>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> requests
<span class="hljs-keyword">from</span> bs4 <span class="hljs-keyword">import</span> BeautifulSoup
<span class="hljs-keyword">import</span> pandas <span class="hljs-keyword">as</span> pd
<span class="hljs-keyword">import</span> os


<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">scrape_matta_members</span>():</span>
    base_url = <span class="hljs-string">"https://www.matta.org.my/members?page="</span>
    members = []

    <span class="hljs-keyword">for</span> page <span class="hljs-keyword">in</span> range(<span class="hljs-number">1</span>, <span class="hljs-number">50</span>):  <span class="hljs-comment"># There are 49 pages. It will loop each file</span>
        url = <span class="hljs-string">f"<span class="hljs-subst">{base_url}</span><span class="hljs-subst">{page}</span>"</span>
        response = requests.get(url)
        <span class="hljs-keyword">if</span> response.status_code != <span class="hljs-number">200</span>:
            print(<span class="hljs-string">f"Failed to load page <span class="hljs-subst">{page}</span>, status code: <span class="hljs-subst">{response.status_code}</span>"</span>)
            <span class="hljs-keyword">continue</span>

        soup = BeautifulSoup(response.content, <span class="hljs-string">'html.parser'</span>)

        <span class="hljs-comment"># Find the section containing the member details</span>
        member_list = soup.find_all(<span class="hljs-string">'div'</span>, class_=<span class="hljs-string">'card-box'</span>)
        <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> member_list:
            print(<span class="hljs-string">f"No members found on page <span class="hljs-subst">{page}</span>"</span>)
            <span class="hljs-keyword">continue</span>

        <span class="hljs-comment"># Iterate over each member entry and extract details</span>
        <span class="hljs-keyword">for</span> member <span class="hljs-keyword">in</span> member_list:
            name = member.find(<span class="hljs-string">'a'</span>, class_=<span class="hljs-string">'search-title'</span>).get_text(strip=<span class="hljs-literal">True</span>)
            reg_number = member.find(<span class="hljs-string">'span'</span>, class_=<span class="hljs-string">'reg-number'</span>).get_text(strip=<span class="hljs-literal">True</span>)
            contact_number = member.find(<span class="hljs-string">'span'</span>, class_=<span class="hljs-string">'contact-number'</span>).get_text(strip=<span class="hljs-literal">True</span>)
            web_address = member.find(<span class="hljs-string">'span'</span>, class_=<span class="hljs-string">'web-address'</span>).get_text(strip=<span class="hljs-literal">True</span>)
            location = member.find(<span class="hljs-string">'span'</span>, class_=<span class="hljs-string">'location'</span>).get_text(separator=<span class="hljs-string">", "</span>, strip=<span class="hljs-literal">True</span>)

            members.append({
                <span class="hljs-string">'name'</span>: name,
                <span class="hljs-string">'reg_number'</span>: reg_number,
                <span class="hljs-string">'contact_number'</span>: contact_number,
                <span class="hljs-string">'web_address'</span>: web_address,
                <span class="hljs-string">'location'</span>: location
            })
        print(<span class="hljs-string">f"Page <span class="hljs-subst">{page}</span> scraped successfully."</span>)

    <span class="hljs-comment"># Convert to DataFrame</span>
    df = pd.DataFrame(members)
    <span class="hljs-keyword">return</span> df


<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">save_to_csv</span>(<span class="hljs-params">df, filename</span>):</span>
    df.to_csv(filename, index=<span class="hljs-literal">False</span>)
    full_path = os.path.abspath(filename)
    print(<span class="hljs-string">f"Data successfully saved to <span class="hljs-subst">{full_path}</span>"</span>)


<span class="hljs-comment"># Run the function and get the dataframe</span>
df = scrape_matta_members()

<span class="hljs-comment"># Save the dataframe to a CSV file</span>
csv_filename = <span class="hljs-string">'matta_members.csv'</span>
save_to_csv(df, csv_filename)
</code></pre>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://github.com/ahmadafif5321/Python/blob/main/webScrapping-%20Matta%20Members.py">https://github.com/ahmadafif5321/Python/blob/main/webScrapping-%20Matta%20Members.py</a></div>
]]></content:encoded></item><item><title><![CDATA[The Power of If]]></title><description><![CDATA[If you save RM8 a day, you'll have RM3000 in a year.
If you read two pages of the Quran a day, you'll finish it twice in a year.
If you walk 10,000 steps every day, it's like running seven marathons in a year.
That's the power of "if".]]></description><link>https://ahmadafif.com/the-power-of-if</link><guid isPermaLink="true">https://ahmadafif.com/the-power-of-if</guid><category><![CDATA[Quotes]]></category><dc:creator><![CDATA[Ahmad Afif]]></dc:creator><pubDate>Sat, 27 Apr 2024 02:25:25 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/NeTPASr-bmQ/upload/a87f766bc3e3fa18ca296a0f97365bc4.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>If you save RM8 a day, you'll have RM3000 in a year.</p>
<p>If you read two pages of the Quran a day, you'll finish it twice in a year.</p>
<p>If you walk 10,000 steps every day, it's like running seven marathons in a year.</p>
<p>That's the power of "if".</p>
]]></content:encoded></item></channel></rss>