<?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[Kay On Data]]></title><description><![CDATA[Let me show you my data world!]]></description><link>https://kayondata.com</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1732578256717/356144a9-b0c8-4ab3-8749-2ccbe7920b7f.gif</url><title>Kay On Data</title><link>https://kayondata.com</link></image><generator>RSS for Node</generator><lastBuildDate>Sun, 12 Apr 2026 03:15:35 GMT</lastBuildDate><atom:link href="https://kayondata.com/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Publishing Powershell Azure SQL VM Toolkit ]]></title><description><![CDATA[In 2021, I wrote a PowerShell script to automate most of the steps for deploying a SQL Server VM on Azure, but not everything. Also back then, the whole deployment process with the script took almost ]]></description><link>https://kayondata.com/publishing-powershell-azure-sql-vm-toolkit</link><guid isPermaLink="true">https://kayondata.com/publishing-powershell-azure-sql-vm-toolkit</guid><category><![CDATA[Azure]]></category><category><![CDATA[Powershell]]></category><category><![CDATA[SQL Server]]></category><category><![CDATA[# sqlserver]]></category><category><![CDATA[vm]]></category><dc:creator><![CDATA[Kay Sauter]]></dc:creator><pubDate>Sun, 08 Mar 2026 20:36:35 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/5fc3e0c00a072175ef2ce189/02e6dc05-65bc-425a-afdf-812c00b55c68.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In 2021, I wrote a PowerShell script to automate most of the steps for deploying a SQL Server VM on Azure, but not everything. Also back then, the whole deployment process with the script took almost one hour to deploy, including database restores and installing chololatey and some tools. Today, I present a new script that does much more, and is faster, easier to configure thanks to YAML. There is only one manual step left: You still have to log into the VM and start a dbatools script to restore your databases. This is not for lack of my willingess but a limitation of the <code>Invoke-AzVMRunCommand</code> method, which means that you'll be the SYSTEM user which is not desirable for database restores.</p>
<p>Why YAML you might wonder? A YAML file is very easy to use, and many infrastructure-as-code tools (like Ansible, Terraform, etc.) use YAML for configuration, so people that are usually working on infrastructure will be pleased to see something like this if a DBA gives them a such YAML file. Additionally, it gives you the possibility to touch only the configuration, but run the same code again for a different deployment.</p>
<p>And for you, as a SQL developer or DBA, you will gain time, as you won't have to click through Azure pages to configure everything, but just quickly edit the YAML file and run the script. Because most of the settings will be similar for you, you don't have to touch every configuration you've made before, either.</p>
<p>You can change the VM size, region, SQL Server version, and more by simply editing the YAML file without touching the script itself. The best part: the entire deployment now takes about 30 minutes from start to finish, including database restores (sample databases by Microsoft; your databases may take longer if they are bigger) and installing tools.</p>
<p>In this post, I'll walk through a PowerShell script that deploys a fully configured SQL Server 2022 VM on Azure. It sets up Azure Bastion for secure access, Key Vault for secrets management, Azure Files for persistent storage, and installs development tools automatically. The entire deployment is driven by a single YAML configuration file.</p>
<h2>The Solution: Config-Driven Deployment</h2>
<p>The solution is split into two files:</p>
<ul>
<li><p><strong>config.yaml</strong>: a declarative configuration file that defines <em>what</em> to deploy</p>
</li>
<li><p><strong>vm_creation_with_bastion.ps1</strong>: the deployment script that makes it happen</p>
</li>
</ul>
<h3>The Configuration File</h3>
<p>Everything that might change between deployments lives in <code>config.yaml</code>:</p>
<pre><code class="language-yaml">resourceGroup:
  name: "YourResourceGroupName"
  location: "Switzerland North"
  tags:
    Purpose: "Demo"

vm:
  size: "Standard_DS13_V2"
  image:
    publisherName: "MicrosoftSQLServer"
    offer: "sql2022-ws2022"
    skus: "sqldev-gen2"
    version: "latest"

storage:
  resourceGroup: "yourstorageRG"
  accountName: "yourstorageaccount"
  fileShareName: "yoursqlbackupsharename"
  driveLetter: "Z"
</code></pre>
<p>Want a different VM size? Change one line. Different region? One line. Different SQL Server version? One line. No script modifications needed.</p>
<h2>Architecture Overview</h2>
<p>The deployment creates resources across two resource groups by design:</p>
<pre><code class="language-plaintext">Azure Subscription
|
+-- Resource Group: YourResourceGroupName (disposable)
|   +-- Azure Bastion
|   +-- SQL Server VM (Managed Identity)
|
+-- Resource Group: yourstorageRG (persistent)
    +-- Key Vault (secrets: vm-admin-password, storage-account-key)
    +-- Storage Account
        +-- File Share (mounted as Z: on VM)
            +-- restore-databases.ps1 (uploaded by script)
</code></pre>
<p><strong>Why two resource groups?</strong> The VM resource group is disposable. You can tear it down and rebuild without losing your Key Vault, storage account, or database backups. The persistent resource group survives VM rebuilds, which matters a lot when you frequently spin up and destroy environments during development.</p>
<h2>Choosing an Azure Bastion SKU</h2>
<p>Azure Bastion comes in different SKU tiers. Check <a href="https://learn.microsoft.com/en-us/azure/bastion/bastion-sku-comparison">https://learn.microsoft.com/en-us/azure/bastion/bastion-sku-comparison</a> for more information.</p>
<h2>Key Design Decisions</h2>
<h3>1. Idempotent Resource Creation</h3>
<p>Every resource is checked before creation. Run the script twice and it won't fail or create duplicates. This is crucial for development environments where you might iterate on the config and re-run the script multiple times.</p>
<h3>2. Key Vault Soft-Delete Handling</h3>
<p>Azure Key Vault has a soft-delete feature that retains deleted vaults for 90 days. If you delete a Key Vault and try to create one with the same name, you will get an error: <em>"The vault name is already in use."</em></p>
<p>The script handles this by automatically incrementing the name.</p>
<p>At the end of the deployment, the script notifies you of the name change so you can update your config for future runs.</p>
<h3>3. Cryptographically Secure Password Storage</h3>
<p>The password is stored in Key Vault immediately and never written to disk. Instead of relying on <code>Get-Random</code> (which uses a PRNG), the script generates VM admin passwords using <code>RNGCryptoServiceProvider</code> and guarantees complexity requirements</p>
<h3>4. Managed Identity for Secrets Access</h3>
<p>The VM uses a system-assigned managed identity to retrieve secrets from Key Vault. No credentials are hardcoded or stored on the VM. The file share mount script, which runs on the VM, fetches the storage account key at runtime.</p>
<p>Even if someone gains access to the VM, they can not extract long-lived credentials. The managed identity token is short-lived and scoped.</p>
<h3>5. Persistent File Share Mount</h3>
<p>The Azure Files share is mounted as a drive letter (Z:) using <code>New-SmbGlobalMapping</code>, which makes it available to all users and services on the VM. A scheduled task re-mounts it on every reboot.</p>
<h3>6. Automated Software Installation</h3>
<p>The script installs development tools on the VM via <code>Invoke-AzVMRunCommand</code>, defined entirely in the YAML config:</p>
<pre><code class="language-yaml">softwareInstalls:
  installScript: |
    choco install vscode git powershell-core tabular-editor -y
    Install-Module -Name dbatools -Force
  logonScript: |
    code --install-extension ms-mssql.mssql
</code></pre>
<p>Some tools (like VS Code extensions) need a user session, so the script registers a one-time logon task that runs at first login and then cleans itself up.</p>
<h3>7. Database Restore Script</h3>
<p>A <code>restore-databases.ps1</code> script is automatically uploaded to the file share. After logging into the VM, you can restore all <code>.bak</code> files from the share with a single command:</p>
<pre><code class="language-plaintext">Z:\restore-databases.ps1
</code></pre>
<p>It uses <a href="https://dbatools.io/">dbatools</a> to iterate over all backup files and restore them to the local SQL Server instance.</p>
<h2>Running the Deployment</h2>
<p>Prerequisites:</p>
<ul>
<li><p>PowerShell 7+ with the <code>Az</code> module installed</p>
</li>
<li><p>An active Azure session (<code>Connect-AzAccount</code>)</p>
</li>
</ul>
<p>Then simply:</p>
<pre><code class="language-powershell">.\vm_creation_with_bastion.ps1
</code></pre>
<p>Or with a custom config:</p>
<pre><code class="language-powershell">.\vm_creation_with_bastion.ps1 -ConfigFile "my-environment.yaml"
</code></pre>
<p>At the end, the script prints the VM credentials and total deployment time:</p>
<pre><code class="language-plaintext">Deployment completed in 00:18:42.

VM Login Credentials:
  Username: youradminusername
  Password: &lt;generated-password&gt;
</code></pre>
<p>I am going to publish a YAML writing helper for SQLVMs so that the picking of a SQLVM image is easier and faster, and it'll write the YAML file for you. Stay tuned for that!</p>
<p>The full source code is available on <a href="https://github.com/kaysauter/azure-sqlvm-toolkit">GitHub</a>.</p>
]]></content:encoded></item><item><title><![CDATA[Deploying Azure SQL VMs with PowerShell Azure Bastion + dbatools]]></title><description><![CDATA[What happened?
About a year ago, my original script from my blog post “Deploy Azure Sql Server Vm With PoSh” (blog, substack) from 2021 broke due to a change in the underlying model. In last August, Microsoft updated its documentation regarding deplo...]]></description><link>https://kayondata.com/deploying-azure-sql-vms-with-powershell-azure-bastion-dbatools</link><guid isPermaLink="true">https://kayondata.com/deploying-azure-sql-vms-with-powershell-azure-bastion-dbatools</guid><category><![CDATA[azure sql vm]]></category><category><![CDATA[Powershell]]></category><category><![CDATA[Infrastructure as code]]></category><category><![CDATA[#IaC]]></category><category><![CDATA[SQL Server]]></category><category><![CDATA[dbatools]]></category><dc:creator><![CDATA[Kay Sauter]]></dc:creator><pubDate>Sat, 29 Mar 2025 19:36:29 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1742229303907/54ae6bc1-bba5-48c6-a7f7-ba044db641c2.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-what-happened">What happened?</h1>
<p>About a year ago, my original script from my blog post “Deploy Azure Sql Server Vm With PoSh” (<a target="_blank" href="https://kayondata.com/deploy-azure-sql-server-vm-with-posh">blog</a>, <a target="_blank" href="https://kayondata.substack.com/p/deploy-azure-sql-server-vm-with-posh">substack</a>) from 2021 broke due to a change in the underlying model. In last August, <a target="_blank" href="https://learn.microsoft.com/en-us/azure/virtual-machines/windows/tutorial-manage-vm">Microsoft updated its documentation</a> regarding deploying Azure <a target="_blank" href="https://learn.microsoft.com/en-us/azure/azure-sql/virtual-machines/scripts/create-sql-vm-powershell?view=azuresql">SQL Server VMs on Azure</a>. Therefore, I was able to redevelop the code, and below, you’ll find the new code along with some new updates that Microsoft made. There are some changes around the model of Azure VMs and the model for <a target="_blank" href="https://learn.microsoft.com/en-us/azure/bastion/design-architecture">Azure Bastion</a>.</p>
<p>In the next chapter, you’ll find a script that deploys an Azure SQL Server VM together with Azure Bastion. Azure Bastion is a service by Microsoft that could be seen as something like “Security as Service” by Microsoft. <a target="_blank" href="https://www.microsoft.com/en-us/security/blog/2020/04/16/security-guidance-remote-desktop-adoption/?msockid=1319a94e459b6aec03e0bde644ec6b7b">If you want to use RDP via open internet, make sure you protect your RDP connection</a>. Azure Bastion covers your RDP connection with a TLS tunnel and it is very easy to use. Another way is to use VPN or a private endpoint, but then you have to manage the network security yourself. If you are just a database engineer / DBA like myself or similar, then possibility don’t know how to do this, and Azure Bastion is an excellent option as it won’t expose your VMs IP-Address to the internet.</p>
<h1 id="heading-for-who-is-this-script">For who is this script?</h1>
<p>If you are a DBA or DB dev at a smaller company or a smaller IT team or just want to have a proof of concept, but your IT team is not able to provide you something like this, this may be for you. Also, if you want to learn Azure Powershell and are a DBA, this is a great start. If you however want to have a solution on a bigger scale, be or adaptions more frequently, you probably will find either <a target="_blank" href="https://learn.microsoft.com/en-us/azure/azure-resource-manager/bicep/overview?tabs=bicep">Bicep</a> by Microsoft or something like <a target="_blank" href="https://learn.microsoft.com/en-us/azure/developer/terraform/overview">Terraform</a> by Hashicorp more helpful. I personally started to develop this script a during the pandemic years when I was a DB developer and wanted to have a platform on which a small team could develop new things and showcase it to customers, even give them the VM as a means to try the data warehouse solution we’ve built for a core banking system provider.</p>
<p>The execution of the whole script, including the installation of <a target="_blank" href="https://dbatools.io/">dbatools</a> takes about 25 mins in total, even though not the whole setup is fully automated yet; I’m working on it to bring it to the fullest automation though. I’ll post a bit more in-depth information about how to adapt this script in future. That post will give you an understanding of the underlying structure that <a target="_blank" href="https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/overview">ARM</a> (Azure Resource Manager) gives you to work with PowerShell, but actually also for anything like Bicep and Terraform, as they also have to work with ARM in the end.</p>
<h1 id="heading-the-new-script">The new script</h1>
<p>I’m not going to give much more details on the script here, but I think I’ve put comments within the script relatively extensively:</p>
<pre><code class="lang-powershell"><span class="hljs-comment"># Existing code</span>
<span class="hljs-variable">$ResourceGroupName</span> = <span class="hljs-string">"sqlvm1"</span>
<span class="hljs-variable">$Location</span> = <span class="hljs-string">"East US"</span>
<span class="hljs-variable">$ResourceGroupParams</span> = <span class="hljs-selector-tag">@</span>{
    Name     = <span class="hljs-variable">$ResourceGroupName</span>
    Location = <span class="hljs-variable">$Location</span>
    Tag      = <span class="hljs-selector-tag">@</span>{<span class="hljs-string">"Purpose"</span> = <span class="hljs-string">"Demo"</span> }
}

<span class="hljs-built_in">New-AzResourceGroup</span> @ResourceGroupParams

<span class="hljs-variable">$SubnetName</span> = <span class="hljs-variable">$ResourceGroupName</span> + <span class="hljs-string">"subnet"</span>
<span class="hljs-variable">$VnetName</span> = <span class="hljs-variable">$ResourceGroupName</span> + <span class="hljs-string">"vnet"</span>
<span class="hljs-variable">$PipName</span> = <span class="hljs-variable">$ResourceGroupName</span> + <span class="hljs-variable">$</span>(<span class="hljs-built_in">Get-Random</span>)

<span class="hljs-comment"># Create a subnet configuration</span>
<span class="hljs-variable">$SubnetConfig</span> = <span class="hljs-built_in">New-AzVirtualNetworkSubnetConfig</span> <span class="hljs-literal">-Name</span> <span class="hljs-variable">$SubnetName</span> `
    <span class="hljs-literal">-AddressPrefix</span> <span class="hljs-number">192.168</span>.<span class="hljs-number">1.0</span>/<span class="hljs-number">24</span>

<span class="hljs-comment"># Create a virtual network</span>
<span class="hljs-variable">$Vnet</span> = <span class="hljs-built_in">New-AzVirtualNetwork</span> <span class="hljs-literal">-ResourceGroupName</span> <span class="hljs-variable">$ResourceGroupName</span> `
    <span class="hljs-literal">-Location</span> <span class="hljs-variable">$Location</span> `
    <span class="hljs-literal">-Name</span> <span class="hljs-variable">$VnetName</span> <span class="hljs-literal">-AddressPrefix</span> <span class="hljs-number">192.168</span>.<span class="hljs-number">0.0</span>/<span class="hljs-number">16</span> `
    <span class="hljs-literal">-Subnet</span> <span class="hljs-variable">$SubnetConfig</span>

<span class="hljs-comment"># Create a public IP address and specify a DNS name</span>
<span class="hljs-variable">$Pip</span> = <span class="hljs-built_in">New-AzPublicIpAddress</span> <span class="hljs-literal">-ResourceGroupName</span> <span class="hljs-variable">$ResourceGroupName</span> <span class="hljs-literal">-Location</span> <span class="hljs-variable">$Location</span> `
    <span class="hljs-literal">-AllocationMethod</span> <span class="hljs-keyword">Static</span> <span class="hljs-literal">-IdleTimeoutInMinutes</span> <span class="hljs-number">4</span> <span class="hljs-literal">-Name</span> <span class="hljs-variable">$PipName</span>

<span class="hljs-comment"># Rule to allow remote desktop (RDP)</span>
<span class="hljs-variable">$NsgRuleRDP</span> = <span class="hljs-built_in">New-AzNetworkSecurityRuleConfig</span> <span class="hljs-literal">-Name</span> <span class="hljs-string">"RDPRule"</span> <span class="hljs-literal">-Protocol</span> Tcp `
    <span class="hljs-literal">-Direction</span> Inbound <span class="hljs-literal">-Priority</span> <span class="hljs-number">1000</span> <span class="hljs-literal">-SourceAddressPrefix</span> * <span class="hljs-literal">-SourcePortRange</span> * `
    <span class="hljs-literal">-DestinationAddressPrefix</span> * <span class="hljs-literal">-DestinationPortRange</span> <span class="hljs-number">3389</span> <span class="hljs-literal">-Access</span> Allow

<span class="hljs-comment"># Rule to allow SQL Server connections on port 1433</span>
<span class="hljs-variable">$NsgRuleSQL</span> = <span class="hljs-built_in">New-AzNetworkSecurityRuleConfig</span> <span class="hljs-literal">-Name</span> <span class="hljs-string">"MSSQLRule"</span>  <span class="hljs-literal">-Protocol</span> Tcp `
    <span class="hljs-literal">-Direction</span> Inbound <span class="hljs-literal">-Priority</span> <span class="hljs-number">1001</span> <span class="hljs-literal">-SourceAddressPrefix</span> * <span class="hljs-literal">-SourcePortRange</span> * `
    <span class="hljs-literal">-DestinationAddressPrefix</span> * <span class="hljs-literal">-DestinationPortRange</span> <span class="hljs-number">1433</span> <span class="hljs-literal">-Access</span> Allow

<span class="hljs-comment"># Create the network security group</span>
<span class="hljs-variable">$NsgName</span> = <span class="hljs-variable">$ResourceGroupName</span> + <span class="hljs-string">"nsg"</span>
<span class="hljs-variable">$Nsg</span> = <span class="hljs-built_in">New-AzNetworkSecurityGroup</span> <span class="hljs-literal">-ResourceGroupName</span> <span class="hljs-variable">$ResourceGroupName</span> `
    <span class="hljs-literal">-Location</span> <span class="hljs-variable">$Location</span> <span class="hljs-literal">-Name</span> <span class="hljs-variable">$NsgName</span> <span class="hljs-literal">-SecurityRules</span> <span class="hljs-variable">$NsgRuleRDP</span>, <span class="hljs-variable">$NsgRuleSQL</span>

<span class="hljs-variable">$InterfaceName</span> = <span class="hljs-variable">$ResourceGroupName</span> + <span class="hljs-string">"int"</span>
<span class="hljs-variable">$Interface</span> = <span class="hljs-built_in">New-AzNetworkInterface</span> <span class="hljs-literal">-Name</span> <span class="hljs-variable">$InterfaceName</span> `
    <span class="hljs-literal">-ResourceGroupName</span> <span class="hljs-variable">$ResourceGroupName</span> <span class="hljs-literal">-Location</span> <span class="hljs-variable">$Location</span> `
    <span class="hljs-literal">-SubnetId</span> <span class="hljs-variable">$VNet</span>.Subnets[<span class="hljs-number">0</span>].Id <span class="hljs-literal">-PublicIpAddressId</span> <span class="hljs-variable">$Pip</span>.Id `
    <span class="hljs-literal">-NetworkSecurityGroupId</span> <span class="hljs-variable">$Nsg</span>.Id

<span class="hljs-comment"># Define a credential object</span>
<span class="hljs-comment"># For a proof of concept or demo, this is fine, but you should really use</span>
<span class="hljs-comment"># a key vault to store your secrets otherwise.</span>
<span class="hljs-variable">$userName</span> = <span class="hljs-string">"azureadmin"</span>
<span class="hljs-variable">$SecurePassword</span> = <span class="hljs-built_in">ConvertTo-SecureString</span> <span class="hljs-string">'YourSecretPassword'</span> `
    <span class="hljs-literal">-AsPlainText</span> <span class="hljs-literal">-Force</span>
<span class="hljs-variable">$Cred</span> = <span class="hljs-built_in">New-Object</span> System.Management.Automation.PSCredential (<span class="hljs-variable">$userName</span>, <span class="hljs-variable">$SecurePassword</span>)

<span class="hljs-comment"># Create a virtual machine configuration</span>
<span class="hljs-comment"># Note that you can change eg. SKU to an SQL Server SKU of your choice, eg</span>
<span class="hljs-comment"># standard or enterprise edition. </span>
<span class="hljs-variable">$VMName</span> = <span class="hljs-variable">$ResourceGroupName</span> + <span class="hljs-string">"VM"</span>
<span class="hljs-variable">$VMConfig</span> = <span class="hljs-built_in">New-AzVMConfig</span> <span class="hljs-literal">-VMName</span> <span class="hljs-variable">$VMName</span> <span class="hljs-literal">-VMSize</span> Standard_DS13_V2 |
<span class="hljs-built_in">Set-AzVMOperatingSystem</span> <span class="hljs-literal">-Windows</span> <span class="hljs-literal">-ComputerName</span> <span class="hljs-variable">$VMName</span> `
    <span class="hljs-literal">-Credential</span> <span class="hljs-variable">$Cred</span> <span class="hljs-literal">-ProvisionVMAgent</span> <span class="hljs-literal">-EnableAutoUpdate</span> |
    <span class="hljs-built_in">Set-AzVMSourceImage</span> <span class="hljs-literal">-PublisherName</span> <span class="hljs-string">"MicrosoftSQLServer"</span> `
    <span class="hljs-literal">-Offer</span> <span class="hljs-string">"sql2022-ws2022"</span> <span class="hljs-literal">-Skus</span> <span class="hljs-string">"sqldev-gen2"</span> <span class="hljs-literal">-Version</span> <span class="hljs-string">"latest"</span> | 
    <span class="hljs-built_in">Add-AzVMNetworkInterface</span> <span class="hljs-literal">-Id</span> <span class="hljs-variable">$Interface</span>.Id

<span class="hljs-comment"># Create the VM</span>
<span class="hljs-built_in">New-AzVM</span> <span class="hljs-literal">-ResourceGroupName</span> <span class="hljs-variable">$ResourceGroupName</span> <span class="hljs-literal">-Location</span> <span class="hljs-variable">$Location</span> <span class="hljs-literal">-VM</span> <span class="hljs-variable">$VMConfig</span>

<span class="hljs-comment"># New code to deploy Azure Bastion</span>
<span class="hljs-variable">$BastionSubnetName</span> = <span class="hljs-string">"AzureBastionSubnet"</span>
<span class="hljs-variable">$BastionSubnetConfig</span> = <span class="hljs-built_in">New-AzVirtualNetworkSubnetConfig</span> `
    <span class="hljs-literal">-Name</span> <span class="hljs-variable">$BastionSubnetName</span> <span class="hljs-literal">-AddressPrefix</span> <span class="hljs-number">192.168</span>.<span class="hljs-number">2.0</span>/<span class="hljs-number">24</span>

<span class="hljs-comment"># Update the virtual network with the Bastion subnet</span>
<span class="hljs-variable">$vnet</span> = <span class="hljs-built_in">Get-AzVirtualNetwork</span> <span class="hljs-literal">-Name</span> <span class="hljs-variable">$VnetName</span> <span class="hljs-literal">-ResourceGroupName</span> <span class="hljs-variable">$ResourceGroupName</span>
<span class="hljs-built_in">Add-AzVirtualNetworkSubnetConfig</span> <span class="hljs-literal">-Name</span> <span class="hljs-variable">$BastionSubnetName</span> `
    <span class="hljs-literal">-VirtualNetwork</span> <span class="hljs-variable">$vnet</span> <span class="hljs-literal">-AddressPrefix</span> <span class="hljs-string">"192.168.2.0/24"</span> | <span class="hljs-built_in">Set-AzVirtualNetwork</span>

<span class="hljs-comment"># Create a public IP address for Bastion (standard sku)</span>
<span class="hljs-variable">$BastionPipName</span> = <span class="hljs-variable">$ResourceGroupName</span> + <span class="hljs-string">"bastionpip"</span>
<span class="hljs-variable">$BastionPip</span> = <span class="hljs-built_in">New-AzPublicIpAddress</span> <span class="hljs-literal">-ResourceGroupName</span> <span class="hljs-variable">$ResourceGroupName</span> <span class="hljs-literal">-Name</span> <span class="hljs-variable">$BastionPipName</span> <span class="hljs-literal">-Location</span> <span class="hljs-variable">$Location</span> `
    <span class="hljs-literal">-AllocationMethod</span> <span class="hljs-keyword">Static</span> <span class="hljs-literal">-IdleTimeoutInMinutes</span> <span class="hljs-number">4</span> <span class="hljs-literal">-Sku</span> Standard

<span class="hljs-comment"># Create the Bastion host</span>
<span class="hljs-variable">$BastionName</span> = <span class="hljs-variable">$ResourceGroupName</span> + <span class="hljs-string">"bastion"</span>
<span class="hljs-built_in">New-AzBastion</span> <span class="hljs-literal">-ResourceGroupName</span> <span class="hljs-variable">$ResourceGroupName</span> <span class="hljs-literal">-Name</span> <span class="hljs-variable">$BastionName</span> `
    <span class="hljs-literal">-PublicIpAddressRgName</span> <span class="hljs-variable">$ResourceGroupName</span> <span class="hljs-literal">-PublicIpAddressName</span> <span class="hljs-variable">$BastionPipName</span> `
    <span class="hljs-literal">-VirtualNetworkRgName</span> <span class="hljs-variable">$ResourceGroupName</span> <span class="hljs-literal">-VirtualNetworkName</span> <span class="hljs-variable">$VnetName</span> <span class="hljs-literal">-Sku</span> <span class="hljs-string">"Basic"</span>

<span class="hljs-variable">$StorageAccountName</span> = <span class="hljs-string">"kaysbackstorageacc"</span>  <span class="hljs-comment"># Use lowercase letters and numbers only</span>
<span class="hljs-variable">$FileShareName</span> = <span class="hljs-string">"sqlbackupshare"</span>

<span class="hljs-comment"># Check if the storage account exists</span>
<span class="hljs-variable">$storageAccount</span> = <span class="hljs-built_in">Get-AzStorageAccount</span> <span class="hljs-literal">-ResourceGroupName</span> <span class="hljs-variable">$ResourceGroupName</span> `
    <span class="hljs-literal">-Name</span> <span class="hljs-variable">$StorageAccountName</span> <span class="hljs-literal">-ErrorAction</span> SilentlyContinue
<span class="hljs-keyword">if</span> (<span class="hljs-operator">-not</span> <span class="hljs-variable">$storageAccount</span>) {
    <span class="hljs-comment"># Create the storage account if it doesn't exist</span>
    <span class="hljs-built_in">New-AzStorageAccount</span> <span class="hljs-literal">-ResourceGroupName</span> <span class="hljs-variable">$ResourceGroupName</span> <span class="hljs-literal">-Name</span> <span class="hljs-variable">$StorageAccountName</span> `
    <span class="hljs-literal">-Location</span> <span class="hljs-variable">$Location</span> <span class="hljs-literal">-SkuName</span> <span class="hljs-string">"Standard_LRS"</span>
    <span class="hljs-built_in">Write-Host</span> <span class="hljs-string">"Storage account '<span class="hljs-variable">$StorageAccountName</span>' created."</span>
}
<span class="hljs-keyword">else</span> {
    <span class="hljs-built_in">Write-Host</span> <span class="hljs-string">"Storage account '<span class="hljs-variable">$StorageAccountName</span>' already exists."</span>
}

<span class="hljs-comment"># Get storage account context</span>
<span class="hljs-variable">$StorageAccountKey</span> = (<span class="hljs-built_in">Get-AzStorageAccountKey</span> <span class="hljs-literal">-ResourceGroupName</span> <span class="hljs-variable">$ResourceGroupName</span> `
    <span class="hljs-literal">-Name</span> <span class="hljs-variable">$StorageAccountName</span>)[<span class="hljs-number">0</span>].Value
<span class="hljs-variable">$Context</span> = <span class="hljs-built_in">New-AzStorageContext</span> <span class="hljs-literal">-StorageAccountName</span> <span class="hljs-variable">$StorageAccountName</span> `
    <span class="hljs-literal">-StorageAccountKey</span> <span class="hljs-variable">$StorageAccountKey</span>

<span class="hljs-comment"># Check if the file share exists</span>
<span class="hljs-variable">$fileShare</span> = <span class="hljs-built_in">Get-AzStorageShare</span> <span class="hljs-literal">-Context</span> <span class="hljs-variable">$Context</span> <span class="hljs-literal">-Name</span> <span class="hljs-variable">$FileShareName</span> `
    <span class="hljs-literal">-ErrorAction</span> SilentlyContinue
<span class="hljs-keyword">if</span> (<span class="hljs-operator">-not</span> <span class="hljs-variable">$fileShare</span>) {
    <span class="hljs-comment"># Create the file share if it doesn't exist</span>
    <span class="hljs-built_in">New-AzStorageShare</span> <span class="hljs-literal">-Context</span> <span class="hljs-variable">$Context</span> <span class="hljs-literal">-Name</span> <span class="hljs-variable">$FileShareName</span>
    <span class="hljs-built_in">Write-Host</span> <span class="hljs-string">"File share '<span class="hljs-variable">$FileShareName</span>' created."</span>
}
<span class="hljs-keyword">else</span> {
    <span class="hljs-built_in">Write-Host</span> <span class="hljs-string">"File share '<span class="hljs-variable">$FileShareName</span>' already exists."</span>
}

<span class="hljs-comment"># install the stuff to the vm</span>
<span class="hljs-variable">$script1</span> = <span class="hljs-string">@"
# Install NuGet and dbatools
Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force
Install-Module -Name dbatools -Force
"@</span>

<span class="hljs-built_in">Invoke-AzVMRunCommand</span> <span class="hljs-literal">-ResourceGroupName</span> <span class="hljs-variable">$ResourceGroupName</span> <span class="hljs-literal">-Name</span> <span class="hljs-variable">$VMName</span> <span class="hljs-literal">-CommandId</span> <span class="hljs-string">'RunPowerShellScript'</span> <span class="hljs-literal">-ScriptString</span> <span class="hljs-variable">$script1</span>
</code></pre>
<h1 id="heading-more-on-azure-bastion">More on Azure Bastion</h1>
<p>So Azure Bastion now comes in some different flavors, which is a relatively new offering. Bastion now has some different SKUs respectively tiers: Basic, Standard and Premium. Furthermore, you can choose from a private-only and developer offer. As for a better understanding of the SKUs, I recommend reading the <a target="_blank" href="https://learn.microsoft.com/en-us/azure/bastion/bastion-overview#sku">quite long feature overview table</a>. The main takeaway is though that if you want to use to an azure sql vm and connect to it via RDP, don’t pick the basic SKU, but a higher tier. The basic SKU won’t support SSL protected RDP connection.</p>
<h1 id="heading-how-to-use-the-script">How to use the script</h1>
<p>I recommend to use Azures Cloud Shell as this is the easiest way to use and you can share the script with your team, too. I like to create a folder with <code>mkdir</code> where you can store such kind of scripts. In the following picture, you’ll see how to get to the Azure Editor that is heavily inspired by vscode.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1743208815357/ff614716-3992-41f2-a9b3-3bf767575eb8.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1743209209797/0b4a990f-9256-4743-b877-c62d39339ec3.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1743209867277/92f6e026-b5ec-4c6e-aa1b-60548df7e47e.png" alt class="image--center mx-auto" /></p>
<p>Create a *.ps1 file with the name you like (see screenshot above, you can use <code>touch</code> to create a file). Open it with the editor (mouse click no. 4 in above image and click no. 5 in below image) and copy &amp; paste the script from above inside. You can save the script with [ctrl]+[s] or via the menu of the editor.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1743233967303/f6e356ab-5a6a-423b-ad88-6bb297097c86.png" alt class="image--center mx-auto" /></p>
<h1 id="heading-more-to-come-for-full-automation">More to come for full automation</h1>
<p>Currently, my script that executes a PowerShell script against the created VM via <code>Invoke-AzVMRunCommand</code>, unfortunately does not create a working attached network storage yet.</p>
<p>There is an issue that I am still investigating and hoping that I can resolve it in the next few weeks. Once I’ll figure that one out (or if you have a tip for me - please let me know!), then I’ll post it to complete this script. Until then you can use the following post deployment script on the vm directly as a workaround:</p>
<pre><code class="lang-powershell"><span class="hljs-comment"># Map the network drive</span>
<span class="hljs-variable">$connectTestResult</span> = <span class="hljs-built_in">Test-NetConnection</span> <span class="hljs-literal">-ComputerName</span> sqldatabasebaks.file.core.windows.net <span class="hljs-literal">-Port</span> <span class="hljs-number">445</span>
<span class="hljs-keyword">if</span> (<span class="hljs-variable">$connectTestResult</span>.TcpTestSucceeded) {
    <span class="hljs-comment"># Save the password so the drive will persist on reboot</span>
    <span class="hljs-comment"># For a proof of concept / demo, this approach is fine, </span>
    <span class="hljs-comment"># but normally, you should want a key vault to store your secrets!</span>
    cmd.exe /C <span class="hljs-string">"cmdkey /add:`"sqldatabasebaks.file.core.windows.net`" /user:`"localhost\sqldatabasebaks`" /pass:`"YourPassword`""</span>
    <span class="hljs-comment"># Mount the drive</span>
    <span class="hljs-built_in">New-PSDrive</span> <span class="hljs-literal">-Name</span> Z <span class="hljs-literal">-PSProvider</span> FileSystem <span class="hljs-literal">-Root</span> <span class="hljs-string">"\\sqldatabasebaks.file.core.windows.net\sqlbaks"</span> <span class="hljs-literal">-Persist</span>
}
<span class="hljs-keyword">else</span> {
    <span class="hljs-built_in">Write-Error</span> <span class="hljs-literal">-Message</span> <span class="hljs-string">"Unable to reach the Azure storage account via port 445. Check to make sure your organization or ISP is not blocking port 445, or use Azure P2S VPN, Azure S2S VPN, or Express Route to tunnel SMB traffic over a different port."</span>
}


<span class="hljs-comment"># copy to mssql folder for restore with dbatools</span>
<span class="hljs-comment"># you may need to adapt the folder path according to your own file storage system</span>
<span class="hljs-built_in">Copy-Item</span> <span class="hljs-literal">-Path</span> <span class="hljs-string">"Z:\*.bak"</span> `
    <span class="hljs-literal">-Destination</span> <span class="hljs-string">"C:\Program Files\Microsoft SQL Server\MSSQL16.MSSQLSERVER\MSSQL\Backup"</span> `
    <span class="hljs-literal">-Recurse</span>


<span class="hljs-comment"># use dbatools for restore dbs</span>
<span class="hljs-built_in">Set-DbatoolsConfig</span> <span class="hljs-literal">-FullName</span> sql.connection.trustcert `
    <span class="hljs-literal">-Value</span> <span class="hljs-variable">$true</span> <span class="hljs-comment"># to ensure that dbatools trusts the certificate</span>
<span class="hljs-built_in">Restore-DbaDatabase</span> <span class="hljs-literal">-SqlInstance</span> sqlvm1VM <span class="hljs-literal">-Path</span> <span class="hljs-string">"C:\Program Files\Microsoft SQL Server\MSSQL16.MSSQLSERVER\MSSQL\Backup"</span>
</code></pre>
<h1 id="heading-how-to-connect-to-the-vm-via-bastion">How to connect to the VM via Bastion</h1>
<p>First, go to your resource group and click on the VM so you get into its information window:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1743231743288/ba75ab9b-e138-435b-9928-5c02deca3508.png" alt class="image--center mx-auto" /></p>
<p>After this, you have 2 ways to connect to the VM via Azure Bastion, both ways are marked with the click no 7 in the screenshot below. Either you use the left side vertical menu or the horizontal menu.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1743232033354/4531743c-ae05-4f31-99b5-f1f9b2f32083.png" alt class="image--center mx-auto" /></p>
<p>After this you get to the menu where you enter the credentials you already used in the script (that is hopefully stored in a key vault). Click on connect.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1743232059468/377f55a8-9903-431d-9565-134997ac3309.png" alt class="image--center mx-auto" /></p>
<p>After this, a new browser tab opens and on the top left, you’ll be asked if you allow to transfer text data to the VM, which you want for this script. In the next screenshot, you’ll see “Zulassen” which is German for “Allow”. Click on allow.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1743273914122/38fcf565-f5c6-452f-8079-3100fc4cb41f.png" alt class="image--center mx-auto" /></p>
<p>For the backups, I like to use Azure File Storage which is a relatively cheap storage. I use this for SQL Server backups. That storage can be mapped to a network drive on the VM. So in order to retrieve the password and as well the script to do so, lets go to its resource group. I recommend to use a different resource group for the storage than the VM. That allows you to delete the VM from time to time to save costs or for a fresh setup. To do so, you simply can delete its resource group, and because your file storage is in another, your backup resource group will stay. If you want to upload backups to your file storage, you can do this very easy this way: Go to your storage account, and here you’ll have the possibility to click on a button that opens Azure Storage Explorer for you.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1743232779712/966b03d3-58f4-4529-8edb-9e9e650b7a08.png" alt class="image--center mx-auto" /></p>
<p>In there, you need to go to your file storage and drag and drop your files to upload. The next screenshots show you how to retrieve the code for your own environment, for your password and your own storage:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1743232664980/9393dec3-f773-4a88-ba27-e6a438c30cc8.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1743232974918/068d4800-05b0-4ac4-b9a4-4d3b61ebf0b9.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1743232988684/4f0fcf66-7990-4df6-97ee-75c381b78692.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1743232997411/01dbe48e-f399-4a32-a55b-654e48ac98ee.png" alt class="image--center mx-auto" /></p>
<p>Use this script and overwrite this part in my deployment script, so you’ll have exactly what you’ll need for your own file storage. After you executed the deployment script (takes about 20 mins) copy the post deployment script (the last code block above) and copy it into PowerShell ISE, then run it:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1743233003640/19e7317d-c363-42f8-87de-cfe8d834e305.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1743233010531/550f1273-6348-4ec6-a5d3-2e68c17c41a7.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1743276191570/72533627-b51c-4cdd-b1f1-b7e561711b2c.png" alt class="image--center mx-auto" /></p>
<p>After you’ve run, open SSMS and make sure that you’ll tick “Trust server certificate” as we don’t have a server certificate on this VM.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1743233043135/8a4acdd9-9096-4718-8d47-576d7609007d.png" alt class="image--center mx-auto" /></p>
<p>Now, finally, you’ll see that, in my example case, AdventureWorks2022 and WideWorldImporters got restored. The restore part was magically done by dbatools, with just a few lines of code!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1743233049291/0870390b-3581-4b75-8163-3f480f09cf21.png" alt class="image--center mx-auto" /></p>
<p>Do you have any questions? Any feedback? Could I have done anything better? You want to give input to improve my code? Let me know in the comments or send me a message!</p>
]]></content:encoded></item><item><title><![CDATA[How to fix the red lines in MS Fabric notebooks]]></title><description><![CDATA[In my last article (hashnode, LinkedIn, Substack) of this series, I covered on how to load csv files in a lakehouse automatically in MS Fabric. In this article, I am going to discuss how we can find and fix errors with notebooks easily.
Firstly, we w...]]></description><link>https://kayondata.com/how-to-fix-the-red-lines-in-ms-fabric-notebooks</link><guid isPermaLink="true">https://kayondata.com/how-to-fix-the-red-lines-in-ms-fabric-notebooks</guid><category><![CDATA[microsoft fabric]]></category><category><![CDATA[Data Science]]></category><category><![CDATA[data-engineering]]></category><category><![CDATA[code first]]></category><category><![CDATA[codefirstapproach]]></category><dc:creator><![CDATA[Kay Sauter]]></dc:creator><pubDate>Mon, 17 Mar 2025 13:29:47 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1741815836555/c8cf4426-8af7-477f-84b5-f722f2029f3f.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In my last article (<a target="_blank" href="https://hashnode.com/post/cm3opi1kl001309k13zk41cs0">hashnode</a>, <a target="_blank" href="https://www.linkedin.com/pulse/loading-files-automatically-bronze-lakehouse-kay-sauter-wxjdf/?trackingId=two%2BRbHsTNC5XoXNIsTExw%3D%3D">LinkedIn</a>, <a target="_blank" href="https://kayondata.substack.com/p/loading-files-automatically-to-bronze-lakehouse">Substack</a>) of this series, I covered on how to load csv files in a lakehouse automatically in MS Fabric. In this article, I am going to discuss how we can find and fix errors with notebooks easily.</p>
<p>Firstly, we want to get a nice overview over all csv files that failed to load. This can be done in a lot of different ways, but I like to do it this way:</p>
<pre><code class="lang-python"><span class="hljs-comment"># check local log</span>
<span class="hljs-keyword">from</span> pyspark.sql.functions <span class="hljs-keyword">import</span> when
df_log = df_log.withColumn(<span class="hljs-string">"Check"</span>, when(df_log[<span class="hljs-string">"row_amount_diff"</span>] == <span class="hljs-number">0</span>, <span class="hljs-string">"✅"</span>).otherwise(<span class="hljs-string">"❌"</span>))
<span class="hljs-comment"># these special characters from [Win Key] + [.] or another smiley picker</span>
</code></pre>
<p>With this, you’ll get this output:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1741816754823/a511308e-d407-4ed5-b8df-c2052f6e5a98.png" alt class="image--center mx-auto" /></p>
<p>If you remember the script in the previous article, the script opened the files first, just to check how many lines it sees in there. Later, the script checks how many lines were loaded and the row amount diff gives back if there was a difference. If yes, it would not be 0, obviously. The last column gives a very nice indication of whether or not a csv file needs attention. If there’s ❌, you probably want to check the line. Note that the columns of a such table are sortable, so you can of course also sort the table after the ❌ signs, and you’ll see all failed loads instantly.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1741817052067/15e5ed93-b744-484b-b4ad-feb6269f9969.png" alt class="image--center mx-auto" /></p>
<p>So next, in the same notebook, I’d like to see if the meta lakehouse actually received the data, too. I use the meta lakehouse for such kinds of events. This serves a lot of purposes: One being able to measure problems in the future. Do always the same files fail? Is there a pattern, maybe they only ever fail on certain dates like every leap year and nobody would know if we wouldn’t have a such log? Or every easter?</p>
<pre><code class="lang-python"><span class="hljs-comment"># check log in meta_lakehouse</span>
df = spark.sql(<span class="hljs-string">f"SELECT * FROM meta_lakehouse.load_log_bronze order by load_timestamp_utc_bronze desc LIMIT <span class="hljs-subst">{csv_count}</span>"</span>)
display(df)
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1741817228906/8c095d98-0b14-4aad-b4a6-79fa1e19fb96.png" alt class="image--center mx-auto" /></p>
<p>So here, it is clear that the lakehouse did indeed work as intended, too.</p>
<p>Now, lets go back to the code that gave us warning in the first place, I actually have another small trick here:</p>
<pre><code class="lang-python"><span class="hljs-comment"># Print the number of tables that gave warnings</span>
<span class="hljs-keyword">if</span> warning_count == <span class="hljs-number">0</span>:
    print(<span class="hljs-string">f"\n\033[92m Number of tables with warnings: NONE - SUCCESS \033[0m"</span>)
<span class="hljs-keyword">else</span>:
    print(<span class="hljs-string">f"\n\033[91m Number of tables with warnings: <span class="hljs-subst">{warning_count}</span> → GO CHECK LOG! \033[0m"</span>)
    displayHTML(<span class="hljs-string">'&lt;a href = "https://app.fabric.microsoft.com/groups/[redacted]" target="_blank"&gt;Click here to get to the notebook to investigate this issue&lt;/a&gt;'</span>)
</code></pre>
<p>The last line, the one I added for this post, adds a link to another notebook (i redacted the exact link to my notebook).</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1741817435049/a8c47295-caf3-4298-a134-d85b8cf5a9b1.png" alt class="image--center mx-auto" /></p>
<p>If I click on this link, I get to the next notebook with wich I can investigate the issue. In other words, notebooks are a great way to give me hints on how I can fix an issue! This new notebook has some similar code, but designed to show you which lines are responsible for the failure:</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> pyspark.sql <span class="hljs-keyword">import</span> SparkSession
<span class="hljs-keyword">from</span> pyspark.sql.functions <span class="hljs-keyword">import</span> monotonically_increasing_id

<span class="hljs-comment"># Path to your CSV file</span>
csv_file = <span class="hljs-string">"Files/Person.Person.csv"</span>

<span class="hljs-comment"># Get the number of lines in the CSV file</span>
lines = spark.read.text(csv_file)
line_count = lines.count() - <span class="hljs-number">1</span> <span class="hljs-comment"># -1 to exclude the header</span>
print(<span class="hljs-string">f"<span class="hljs-subst">{csv_file}</span> should have this amount of lines: <span class="hljs-subst">{line_count:,}</span>"</span>.replace(<span class="hljs-string">","</span>, <span class="hljs-string">"'"</span>))

<span class="hljs-comment"># Read the CSV file with PERMISSIVE mode to get all records</span>
df_all = spark.read.options(
    delimiter=<span class="hljs-string">","</span>,
    header=<span class="hljs-literal">True</span>,
    encoding=<span class="hljs-string">"UTF-8"</span>,
    inferSchema=<span class="hljs-literal">True</span>,
    multiLine=<span class="hljs-literal">True</span>,
    escapeQuotes=<span class="hljs-literal">True</span>,
    mode=<span class="hljs-string">"PERMISSIVE"</span>
).csv(csv_file)

<span class="hljs-comment"># Read the CSV file with DROPMALFORMED mode to get only valid records</span>
df_well = spark.read.options(
    delimiter=<span class="hljs-string">","</span>,
    header=<span class="hljs-literal">True</span>,
    encoding=<span class="hljs-string">"UTF-8"</span>,
    inferSchema=<span class="hljs-literal">True</span>,
    multiLine=<span class="hljs-literal">True</span>,
    escapeQuotes=<span class="hljs-literal">True</span>,
    mode=<span class="hljs-string">"DROPMALFORMED"</span>
).csv(csv_file)

<span class="hljs-comment"># Identify malformed records by finding the difference</span>
df_mal = df_all.subtract(df_well)

<span class="hljs-comment"># Add a record number to the malformed records DataFrame</span>
df_mal = df_mal.withColumn(<span class="hljs-string">"record_number"</span>, monotonically_increasing_id())
columns = [<span class="hljs-string">'record_number'</span>] + [col <span class="hljs-keyword">for</span> col <span class="hljs-keyword">in</span> df_mal.columns <span class="hljs-keyword">if</span> col != <span class="hljs-string">'record_number'</span>]
df_mal = df_mal.select(columns)

<span class="hljs-comment"># Show the malformed records with their record numbers</span>
print(<span class="hljs-string">f"The amount of read well formed lines: <span class="hljs-subst">{df_well.count():,}</span>"</span>.replace(<span class="hljs-string">","</span>,<span class="hljs-string">"'"</span>))
print(df_mal.count())
display(df_mal)
</code></pre>
<p>This returns me this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1741817855706/2eb34a56-28b6-4a31-8c82-b04bf1b968cf.png" alt class="image--center mx-auto" /></p>
<p>So the print output tells me that there was a load failure because of 4 lines. Those 4 lines are visible in the table. Upon investigation of this table, you can notice that something in the column “Demographics” doesn’t add up, hence the failure must have happened in the column before. Most likely, a character is responsible for this, as we see in “AdditionalContactInfo” XML data which is very prone to failures like this. So either a character needs to be taken care of or XML data in general should be attacked differently, eg. in an own table. Another possible solution is to avoid XML altogether and just scrap everything in there in a different way so that XML never gets loaded. I’m sure there could be other ideas for this, too. If you want, let me know in the comments!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1741818056094/d2152de5-b4d2-422f-a1e1-6821c653d2b1.png" alt class="image--center mx-auto" /></p>
<p>I think this notebook also should give the person who is investigating this issue an idea of other incidents. A such overview is achieved very easily:</p>
<pre><code class="lang-python">df = spark.sql(<span class="hljs-string">"SELECT * FROM meta_lakehouse.bad_records LIMIT 1000"</span>)
display(df)
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1741818421820/84b3a847-74dc-40bc-837e-b179b6fdb6d0.png" alt class="image--center mx-auto" /></p>
<p>So this is another great example of make use of a meta lakehouse that may support you to investigate such issues. If you click on “entire_record”, you actually indeed can see the entire record:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1741818520440/2d523732-9405-443b-85e9-966c57ed49de.png" alt class="image--center mx-auto" /></p>
<p>So I think I've shown with this that notebooks are a really great way to support people on investigating issues. Meta lakehouses are obviously not meant to be accessible for everyone, but they may have data that business users may find helpful, eg. if you have a Power BI report that you want give an indication that the data is not fresh for this specific report yet. Make use of your own comments and your own failure reports so that you find your issue easier and quicker instead of drowning in error logs. Notebooks do have error logs, and they may be helpful too, but they have log a lot of information that may make it a bit convoluted to point you to your solution to the problem. However, I will cover this in a future post.</p>
<p>If you have any questions, I’m looking forward to get them in the comments below! Or if you have any suggestions on what I could improve in them: I am all ears!</p>
<p>Cover image credit: created with Microsoft Bings CoPilot.</p>
]]></content:encoded></item><item><title><![CDATA[I've moved my blog on kayondata.com to Hashnode!]]></title><description><![CDATA[I was actually quite happy with Hugo CMS, for it is a great blog system. Unlike the most popular CMS out here, it doesn’t really need any frequent updates to be secure. It uses Markdown which I love, and I love to use within my favorite code editor, ...]]></description><link>https://kayondata.com/ive-moved-my-blog-on-kayondatacom-to-hashnode</link><guid isPermaLink="true">https://kayondata.com/ive-moved-my-blog-on-kayondatacom-to-hashnode</guid><category><![CDATA[Editorial]]></category><category><![CDATA[blog]]></category><dc:creator><![CDATA[Kay Sauter]]></dc:creator><pubDate>Mon, 02 Dec 2024 07:30:20 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1733085317557/89e944cf-a4ef-43c4-b315-20f28ad2385f.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I was actually quite happy with <a target="_blank" href="https://gohugo.io/">Hugo CMS</a>, for it is a great blog system. Unlike the most popular CMS out here, it doesn’t really need any frequent updates to be secure. It uses Markdown which I love, and I love to use within my favorite code editor, <a target="_blank" href="https://code.visualstudio.com/">VSCode</a>. However, Hugo had its downsides. Because you have to start a server on your computer to see its results, you always had to use something like <code>hugo -D</code> etc. and remember how to create a new blog with something like <code>hugo new content content/posts/</code><a target="_blank" href="http://my-first-post.md"><code>my-first-post.md</code></a>, actually a quite good idea. I would, however, always forget how to create a new post and have to wait to see the post in the browser. Lastly, with Github Actions, I would have to patiently wait until it is uploaded. I was quite liking it however. The reason I made the switch though were two things: I somehow messed up my repo, possibly due to the Hugo generator update (the software you install on your laptop locally). Secondly, I actually wanted to have a blog that people could add comments to, and a subscription newsletter. The comment part could have been solved with Github, but it is convoluted to create and depends on the template you use. The newsletter part however, was much trickier. Some were quite expensive, others had strange limitations. With <a target="_blank" href="https://hashnode.com/">Hashnode</a>, I finally found something that solves both problems, and even a very nice, easy to use workflow for blogging. I even can use Github again here, I am using it for backing up my blogs. The domain is the same as before: <a target="_blank" href="http://kayondata.com">kayondata.com</a>.</p>
<p>The move was quite easy, especially with the great support by Hashnode! I received the answers to my questions within a few hours in most cases, but never more a half day!</p>
<p>So if you are looking for a possibility for a newsletter outside of my <a target="_blank" href="https://www.linkedin.com/newsletters/kay-on-data-7043592227159207936/">LinkedIn Newsletter</a>, you can now use Hashnode to subscribe to my posts. Additionally, you can use RSS here, too.</p>
<p>I hope you will subscribe to both my newsletters, on <a target="_blank" href="http://kayondata.com">kayondata.com</a> and on <a target="_blank" href="https://www.linkedin.com/newsletters/kay-on-data-7043592227159207936/">LinkedIn</a>.</p>
<p>Credit: Cover picture generated by CoPilot.</p>
]]></content:encoded></item><item><title><![CDATA[Loading files automatically to bronze lakehouse]]></title><description><![CDATA[In my last post on LinkedIn, I explained how to export AdventureWorks2022 tables to csv files. If you don’t want to generate them, you can get them here, as stated in the last post (after my edit in which I realized I made a mistake). My actual blog ...]]></description><link>https://kayondata.com/loading-files-automatically-to-bronze-lakehouse</link><guid isPermaLink="true">https://kayondata.com/loading-files-automatically-to-bronze-lakehouse</guid><category><![CDATA[data-engineering]]></category><category><![CDATA[Python]]></category><category><![CDATA[ms fabric]]></category><category><![CDATA[data lakehouse]]></category><dc:creator><![CDATA[Kay Sauter]]></dc:creator><pubDate>Tue, 19 Nov 2024 17:06:17 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1731967868018/082389d2-7f93-4428-a853-2023769d5889.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In my last <a target="_blank" href="https://www.linkedin.com/pulse/getting-adventureworks-fabric-kay-sauter-l1qpf/?trackingId=JL%2FNed7HQxigHSrBVOSroQ%3D%3D">post on LinkedIn</a>, I explained how to export AdventureWorks2022 tables to csv files. If you don’t want to generate them, you can get them <a target="_blank" href="https://github.com/kaysauter/getting-adventureworks-to-fabric">here</a>, as stated in the last post (after my edit in which I realized I made a mistake). My actual blog <a target="_blank" href="https://www.kayondata.com/">https://kayondata.com</a> is currently being moved to another place, hence not updated at the moment.</p>
<p>Certainly, it is best to avoid CSV files today. However, CSV files are still very common, so I am using this approach first, but I plan on writing other posts on other ways to do this. Another reason why I am using CSV files is the fact that this is also a very nice starting point to learn some basics and to exercise some data engineering stuff. Nevertheless, if you find yourself in a situation of choosing loading directly from a database without files, usually, this is the better route. If you have the possibility to use parquet instead of CSV, usually parquet should be the winner in the decision.</p>
<p>That said, let’s start with some code that I’ve written some months back on runtime 1.2 (Spark 3.4, Delta 2.4), but it should work on runtime 1.3 (Spark 3.5, Delta 3.2) as well, according to my test today. The code does quite some stuff, and it expects you to have an existing lakehouse that your notebook is attached to. Moreover it expects you to have a lakehouse called <em>meta_lakehouse</em> which is used to log the data loads and errors. You’ll see an output that returns some simple plausibility checks:</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> os, time, datetime 
<span class="hljs-keyword">from</span> pyspark.sql.functions <span class="hljs-keyword">import</span> monotonically_increasing_id, lit, current_timestamp, when, col, regexp_replace, array, concat_ws
<span class="hljs-keyword">from</span> pyspark.sql <span class="hljs-keyword">import</span> Row
<span class="hljs-keyword">from</span> pyspark.sql.types <span class="hljs-keyword">import</span> StructType, StructField, StringType, IntegerType, TimestampType
<span class="hljs-keyword">from</span> notebookutils <span class="hljs-keyword">import</span> mssparkutils


<span class="hljs-comment"># Specify the folder containing CSV files</span>
csv_folder = <span class="hljs-string">"Files"</span>

<span class="hljs-comment"># Get a list of all files in the folder</span>
files = notebookutils.fs.ls(csv_folder)

<span class="hljs-comment"># Filter for CSV files</span>
csv_files = [file.path <span class="hljs-keyword">for</span> file <span class="hljs-keyword">in</span> files <span class="hljs-keyword">if</span> file.name.endswith(<span class="hljs-string">".csv"</span>)] <span class="hljs-comment"># to test only one file, simply write the name before .csv</span>

<span class="hljs-comment"># Define the schema for the log DataFrame so it has have good column names</span>
log_schema = StructType([
    StructField(<span class="hljs-string">"file_name"</span>, StringType(), <span class="hljs-literal">True</span>),
    StructField(<span class="hljs-string">"row_amount_file"</span>, IntegerType(), <span class="hljs-literal">True</span>),
    StructField(<span class="hljs-string">"row_amount_table"</span>, IntegerType(), <span class="hljs-literal">True</span>),
    StructField(<span class="hljs-string">"row_amount_diff"</span>, IntegerType(), <span class="hljs-literal">True</span>),
    StructField(<span class="hljs-string">"load_timestamp_utc_bronze"</span>, TimestampType(), <span class="hljs-literal">True</span>)
])

<span class="hljs-comment"># Define the schema for the malformed records DataFrame so it has good column names</span>
malformed_schema = StructType([
    StructField(<span class="hljs-string">"file_name"</span>, StringType(), <span class="hljs-literal">True</span>),
    StructField(<span class="hljs-string">"record_number"</span>, IntegerType(), <span class="hljs-literal">True</span>),
    StructField(<span class="hljs-string">"entire_record"</span>, StringType(), <span class="hljs-literal">True</span>),
    StructField(<span class="hljs-string">"explanation"</span>, StringType(), <span class="hljs-literal">True</span>),
    StructField(<span class="hljs-string">"timestamp"</span>, TimestampType(), <span class="hljs-literal">True</span>)
])

<span class="hljs-comment"># Initialize an empty DataFrame for log entries with the defined schema</span>
df_log = spark.createDataFrame([], schema=log_schema)
df_mal = spark.createDataFrame([], schema=malformed_schema)
df_mal_log = spark.createDataFrame([], schema=malformed_schema)

<span class="hljs-comment"># Initialize a counter for warnings</span>
warning_count = <span class="hljs-number">0</span>

<span class="hljs-comment"># count files</span>
csv_files_count = [file <span class="hljs-keyword">for</span> file <span class="hljs-keyword">in</span> files <span class="hljs-keyword">if</span> file.name.endswith(<span class="hljs-string">'.csv'</span>)]
csv_count = len(csv_files_count)
file_processed_count = <span class="hljs-number">0</span>

<span class="hljs-comment"># Process each CSV file</span>
<span class="hljs-keyword">for</span> csv_file <span class="hljs-keyword">in</span> csv_files:
    file_processed_count += <span class="hljs-number">1</span>
    table_name = os.path.splitext(os.path.basename(csv_file))[<span class="hljs-number">0</span>] <span class="hljs-comment"># Extract table name from the file name (remove ".csv" extension)</span>
    table_name = table_name.replace(<span class="hljs-string">"."</span>,<span class="hljs-string">"_"</span>) <span class="hljs-comment"># remove "." for database schema and replace it with "_"</span>

    print(<span class="hljs-string">f"<span class="hljs-subst">{table_name}</span>.csv is being processed. Remaining files to process: <span class="hljs-subst">{csv_count - file_processed_count}</span>"</span>) 

    <span class="hljs-comment"># Get the number of lines in the CSV file</span>
    lines = spark.read.text(csv_file)
    line_count = lines.count() - <span class="hljs-number">1</span> <span class="hljs-comment"># -1 to exclude the header</span>
    print(<span class="hljs-string">f"<span class="hljs-subst">{table_name}</span> has this amount of lines: <span class="hljs-subst">{line_count:,}</span>"</span>.replace(<span class="hljs-string">","</span>, <span class="hljs-string">"'"</span>))

    <span class="hljs-comment"># Read the CSV file with PERMISSIVE mode to get all records</span>
    df_all = spark.read.options(
        delimiter=<span class="hljs-string">","</span>, 
        header=<span class="hljs-literal">True</span>,
        encoding=<span class="hljs-string">"UTF-8"</span>, 
        inferSchema=<span class="hljs-literal">True</span>, 
        multiLine=<span class="hljs-literal">True</span>,
        escapeQuotes=<span class="hljs-literal">True</span>, 
        mode=<span class="hljs-string">"PERMISSIVE"</span>
    ).csv(csv_file)

    <span class="hljs-comment"># Read the CSV file with DROPMALFORMED mode to get only valid records</span>
    df_well = spark.read.options(
        delimiter=<span class="hljs-string">","</span>, 
        header=<span class="hljs-literal">True</span>,
        encoding=<span class="hljs-string">"UTF-8"</span>, 
        inferSchema=<span class="hljs-literal">True</span>, 
        multiLine=<span class="hljs-literal">True</span>,
        escapeQuotes=<span class="hljs-literal">True</span>, 
        mode=<span class="hljs-string">"DROPMALFORMED"</span>
    ).csv(csv_file)

    <span class="hljs-comment"># Identify malformed records by diff it</span>
    df_mal = df_all.subtract(df_well)

    <span class="hljs-comment"># Add a record number to the malformed records dataFrame</span>
    df_mal = df_mal.withColumn(<span class="hljs-string">"record_number"</span>, monotonically_increasing_id())
    columns = [<span class="hljs-string">'record_number'</span>] + [col <span class="hljs-keyword">for</span> col <span class="hljs-keyword">in</span> df_mal.columns <span class="hljs-keyword">if</span> col != <span class="hljs-string">'record_number'</span>]
    df_mal = df_mal.select(columns)

    <span class="hljs-comment"># Show the malformed records with their record numbers</span>
    print(<span class="hljs-string">f"The amount of read well formed lines: <span class="hljs-subst">{df_well.count():,}</span>"</span>.replace(<span class="hljs-string">","</span>,<span class="hljs-string">"'"</span>))
    <span class="hljs-keyword">if</span> df_mal.count() == <span class="hljs-number">0</span>:
        print(<span class="hljs-string">f"\033[92mThe amount of read mal formed lines:  <span class="hljs-subst">{df_mal.count():,}</span>\033[0m"</span>.replace(<span class="hljs-string">","</span>,<span class="hljs-string">"'"</span>))
    <span class="hljs-keyword">else</span>:    
        print(<span class="hljs-string">f"\033[91mThe amount of read mal formed lines:  <span class="hljs-subst">{df_mal.count():,}</span>\033[0m"</span>.replace(<span class="hljs-string">","</span>,<span class="hljs-string">"'"</span>)) 
        <span class="hljs-comment"># display(df_mal)</span>

    <span class="hljs-comment"># Add id column and put it into the front </span>
    df_well = df_well.withColumn(<span class="hljs-string">"id"</span>, monotonically_increasing_id())  
    columns_ordered = [<span class="hljs-string">"id"</span>] + [col <span class="hljs-keyword">for</span> col <span class="hljs-keyword">in</span> df_well.columns <span class="hljs-keyword">if</span> col != <span class="hljs-string">"id"</span>]
    df_well = df_well.select(*columns_ordered)

    <span class="hljs-comment"># Add load_timestamp</span>
    df_well = df_well.withColumn(<span class="hljs-string">'load_timestamp_utc_bronze'</span>, current_timestamp())

    <span class="hljs-comment"># Write to Delta table</span>
    df_well.write.format(<span class="hljs-string">"delta"</span>).mode(<span class="hljs-string">"overwrite"</span>).option(<span class="hljs-string">"overwriteSchema"</span>, <span class="hljs-string">"true"</span>).saveAsTable(table_name)

    <span class="hljs-comment"># Check the number of rows in the Delta table</span>
    delta_table = spark.table(table_name)
    delta_count = delta_table.count()
    print(<span class="hljs-string">f"<span class="hljs-subst">{table_name}</span> has this amount of lines in the delta table: <span class="hljs-subst">{delta_count:,}</span>"</span>.replace(<span class="hljs-string">","</span>, <span class="hljs-string">"'"</span>))

    <span class="hljs-comment"># Create a dataframe for the current log entry</span>
    df_current_log = spark.createDataFrame([(
        table_name,
        line_count,
        delta_count,
        line_count - delta_count,
        datetime.datetime.now()  <span class="hljs-comment"># Use current timestamp</span>
    )], schema=log_schema)

    <span class="hljs-comment"># Append the current log entry to the log DataFrame</span>
    df_log = df_log.union(df_current_log)

    <span class="hljs-comment"># Append the current malformed dataframe to a log dataframe</span>
    <span class="hljs-comment"># display(df_mal)</span>
    <span class="hljs-comment"># df_mal_log = df_mal_log.union(df_mal)</span>

    <span class="hljs-comment"># Prepare the malformed records for logging</span>
    df_mal_log_entry = df_mal.withColumn(<span class="hljs-string">"file_name"</span>, lit(table_name)) \
                             .withColumn(<span class="hljs-string">"entire_record"</span>, concat_ws(<span class="hljs-string">"|||"</span>, *df_mal.columns)) \
                             .withColumn(<span class="hljs-string">"explanation"</span>, lit(<span class="hljs-string">"malformed"</span>)) \
                             .withColumn(<span class="hljs-string">"timestamp"</span>, current_timestamp()) \
                             .withColumn(<span class="hljs-string">"record_number"</span>, monotonically_increasing_id())

    <span class="hljs-comment"># Select the columns in the desired order</span>
    df_mal_log_entry = df_mal_log_entry.select(<span class="hljs-string">"file_name"</span>, <span class="hljs-string">"record_number"</span>, <span class="hljs-string">"entire_record"</span>, <span class="hljs-string">"explanation"</span>, <span class="hljs-string">"timestamp"</span>)

    <span class="hljs-comment"># Append the current malformed records to the log DataFrame</span>
    df_mal_log = df_mal_log.union(df_mal_log_entry)

    <span class="hljs-comment"># Compare the counts and print messages</span>
    <span class="hljs-keyword">if</span> line_count == delta_count:
        print(<span class="hljs-string">f"\033[92mSUCCESS: <span class="hljs-subst">{table_name}</span>.csv loaded into Delta table <span class="hljs-subst">{table_name}</span> with matching line counts\033[0m"</span>)
        print(<span class="hljs-string">f"\n"</span>)
    <span class="hljs-keyword">else</span>:
        print(<span class="hljs-string">f"\033[91mERROR: <span class="hljs-subst">{table_name}</span>.csv line count <span class="hljs-subst">{line_count:,}</span>"</span>.replace(<span class="hljs-string">","</span>, <span class="hljs-string">"'"</span>) + <span class="hljs-string">f" does not match Delta table line count <span class="hljs-subst">{delta_count:,}</span>"</span>.replace(<span class="hljs-string">","</span>, <span class="hljs-string">"'"</span>) + <span class="hljs-string">f" \033[0m"</span>)
        print(<span class="hljs-string">f"\n"</span>)
        warning_count += <span class="hljs-number">1</span>

<span class="hljs-comment"># Write the log DataFrame to the lakehouse log table in the meta_lakehouse database</span>
<span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> df_log.filter(df_log[<span class="hljs-string">"row_amount_diff"</span>] &gt; <span class="hljs-number">0</span>).rdd.isEmpty():
    df_log.write.format(<span class="hljs-string">"delta"</span>).mode(<span class="hljs-string">"append"</span>).saveAsTable(<span class="hljs-string">"meta_lakehouse.load_log_bronze"</span>)
    print(<span class="hljs-string">f"\n\033[93m ALL CSV files LOADED with WARNING into meta_lakehouse.load_log_bronze table.\033[0m"</span>)
<span class="hljs-keyword">else</span>:
    print(<span class="hljs-string">f"\n\033[92m ALL CSV files SUCCESSFULLY LOADED into meta_lakehouse.load_log_bronze table.\033[0m"</span>)

<span class="hljs-comment"># Write the malformed records DataFrame to a new Delta table in the meta_lakehouse database</span>
<span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> df_mal_log.rdd.isEmpty():
    df_mal_log.write.format(<span class="hljs-string">"delta"</span>).mode(<span class="hljs-string">"append"</span>).saveAsTable(<span class="hljs-string">"meta_lakehouse.bad_records"</span>)
    print(<span class="hljs-string">f"\n\033[93m Malformed records have been written to the meta_lakehouse.bad_records table.\033[0m"</span>)

<span class="hljs-comment"># Print the number of tables that gave warnings</span>
<span class="hljs-keyword">if</span> warning_count == <span class="hljs-number">0</span>:
    print(<span class="hljs-string">f"\n\033[92m Number of tables with warnings: NONE - SUCCESS \033[0m"</span>)
<span class="hljs-keyword">else</span>:
    print(<span class="hljs-string">f"\n\033[91m Number of tables with warnings: <span class="hljs-subst">{warning_count}</span> → GO CHECK LOG! \033[0m"</span>)
</code></pre>
<p>So if you run this, you’ll see a lot outputs like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1731968713774/0b90e070-cc43-442f-96fc-1e488c6f0a60.png" alt class="image--center mx-auto" /></p>
<p>As you can see, it outputs some table names and csv names. The csv-names are not actually very correct here, because the actual csv name is HumanResources.Departement.csv, so there is a dot that derives from the database schema. In this case, I am using a lakehouse without schema (it is still in preview) and the lakehouse does not allow points in a table name, hence I replaced the point with an underscore in line 53 via <code>table_name = table_name.replace(".","_")</code>, as also the comment after the very code explains.</p>
<p>Now, you could ask why I didn’t want to use shortcuts to load the data - I could have done this and it is a neat trick for many occasions. In this case, I not only wanted to showcase the possibility to use a meta_lakehouse, but also the possibility to have more control in the data that gets loaded here. For example, I get the possiblity to have some simple plausibility checks that certainly could be done much more sophisticated. This is going to be another blog in future.</p>
<p>An interesting code block is here (line 62):</p>
<pre><code class="lang-python">    <span class="hljs-comment"># Read the CSV file with PERMISSIVE mode to get all records</span>
    df_all = spark.read.options(
        delimiter=<span class="hljs-string">","</span>, 
        header=<span class="hljs-literal">True</span>,
        encoding=<span class="hljs-string">"UTF-8"</span>, 
        inferSchema=<span class="hljs-literal">True</span>, 
        multiLine=<span class="hljs-literal">True</span>,
        escapeQuotes=<span class="hljs-literal">True</span>, 
        mode=<span class="hljs-string">"PERMISSIVE"</span>
    ).csv(csv_file)

    <span class="hljs-comment"># Read the CSV file with DROPMALFORMED mode to get only valid records</span>
    df_well = spark.read.options(
        delimiter=<span class="hljs-string">","</span>, 
        header=<span class="hljs-literal">True</span>,
        encoding=<span class="hljs-string">"UTF-8"</span>, 
        inferSchema=<span class="hljs-literal">True</span>, 
        multiLine=<span class="hljs-literal">True</span>,
        escapeQuotes=<span class="hljs-literal">True</span>, 
        mode=<span class="hljs-string">"DROPMALFORMED"</span>
    ).csv(csv_file)
</code></pre>
<p>Firstly, we get to give pySpark the possiblity to load all data and to try to figure out what datatypes the fields are. delimiter=”,” tells pySpark that it should use commatas, but you could use semicolon if needed, or another character. <code>inferSchema=True</code> is actually superfluous here as the <code>spark.read.options</code> uses this per default, but I think it is always a good idea to be explicit. In this case, the code reads the whole file, and for other file formats other than csv, you can tweak this with samplingRatio. There are many other options you can use here, and there’s a quite comprehensive <a target="_blank" href="https://spark.apache.org/docs/latest/sql-data-sources-csv.html">documentation here</a>. Be aware that inferring a schema based on a whole file isn’t what you want in all cases, especially if your file is too big. If you are aiming for speedy loads, then you should not using inferring schema, but define the targeted schema from the start. In this case, with relatively small amount of data of an AdventureWorks2022 sample data, I think this is a good showcase. By the way, with this you will also be able to get the schema pretty easy.</p>
<p>Another trick I am showcasing here is the possiblity to get the data that were malformed reads with a diff (line 84):</p>
<pre><code class="lang-python">    <span class="hljs-comment"># Identify malformed records by diff it</span>
    df_mal = df_all.subtract(df_well)
</code></pre>
<p>Last thing I want to point out for today is the possiblity to write out statements in colors (line 154):</p>
<pre><code class="lang-python"><span class="hljs-comment"># Write the log DataFrame to the lakehouse log table in the meta_lakehouse database</span>
<span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> df_log.filter(df_log[<span class="hljs-string">"row_amount_diff"</span>] &gt; <span class="hljs-number">0</span>).rdd.isEmpty():
    df_log.write.format(<span class="hljs-string">"delta"</span>).mode(<span class="hljs-string">"append"</span>).saveAsTable(<span class="hljs-string">"meta_lakehouse.load_log_bronze"</span>)
    print(<span class="hljs-string">f"\n\033[93m ALL CSV files LOADED with WARNING into meta_lakehouse.load_log_bronze table.\033[0m"</span>)
<span class="hljs-keyword">else</span>:
    print(<span class="hljs-string">f"\n\033[92m ALL CSV files SUCCESSFULLY LOADED into meta_lakehouse.load_log_bronze table.\033[0m"</span>)
</code></pre>
<p>The strange code within print(f” ) is a code for coloring text. The difference we see in the warning output and the success output is <code>[93m</code> (yellow) and <code>[92m</code> (green)</p>
<p>At the very end, there’s an output like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1731970683000/502acd90-8fad-4397-91fa-0bd4e04b2f42.png" alt class="image--center mx-auto" /></p>
<p>The last line hints that the next blog post will be about how to find the issues easily and how to fix them. Notebooks are great to instruct people on what to do, especially if they have links to the next notebook!</p>
<p>Do you have any improvements I could have made in my code? Or do you have a question you want to discuss? Even better, found any mistake? Let me know in the comments!</p>
<p>This post was first published on <a target="_blank" href="https://community.fabric.microsoft.com/t5/Data-Engineering-Community-Blog/Loading-files-automatically-to-bronze-lakehouse/ba-p/4289735">Fabric community blogs</a> for data engineering. Credit: Cover image was created by DALL-E by Microsoft CoPilot.</p>
]]></content:encoded></item><item><title><![CDATA[Getting Adventureworks to Fabric]]></title><description><![CDATA[Today, I am starting a series of blog posts on how to get the AdventureWorks database to Fabric. This is a series of blog posts that will cover a journey from exporting the AdventureWorks database to csv files, to getting the data to Fabric Lakehouse...]]></description><link>https://kayondata.com/getting-adventureworks-to-fabric</link><guid isPermaLink="true">https://kayondata.com/getting-adventureworks-to-fabric</guid><category><![CDATA[ms fabric]]></category><category><![CDATA[adventure works]]></category><category><![CDATA[Tutorial]]></category><dc:creator><![CDATA[Kay Sauter]]></dc:creator><pubDate>Tue, 20 Aug 2024 22:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1732577131265/bb96b172-480e-45bc-9d5d-8f5225aa373b.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Today, I am starting a series of blog posts on how to get the AdventureWorks database to Fabric. This is a series of blog posts that will cover a journey from exporting the AdventureWorks database to csv files, to getting the data to Fabric Lakehouse and then to the Data Warehouse. Along the way, I will be discussing some approaches and tips.</p>
<p>If you don't know the AdventureWorks database, it is a sample database by Microsoft. It is used to demonstrate the capabilities of SQL Server. The database is used in many examples, tutorials and documentation by Microsoft. You can download the database <a target="_blank" href="https://learn.microsoft.com/en-us/sql/samples/adventureworks-install-configure?view=sql-server-ver16&amp;tabs=ssms">here</a>.</p>
<p>There are many versions of it, and i'll be using the AdventureWorks2022 database. I've got my database on a local SQL Server instance. The first step is to export the data to csv files. If you're using a different database, please make sure that it isn't a productive database as exporting all data to csv files can be expensive process. This is not to say that it can't be used in a productive environment, but test it on a non-productive environment first. This way you'll be able to see how long it takes and how much cpu and memory it uses on your system.</p>
<p>So, let me walk you through the Python code by the comments within it:</p>
<pre><code class="lang-python"><span class="hljs-comment"># importing some libraries</span>
<span class="hljs-keyword">import</span> pathlib <span class="hljs-keyword">as</span> pl
<span class="hljs-keyword">import</span> pyodbc
<span class="hljs-keyword">import</span> pandas <span class="hljs-keyword">as</span> pd

<span class="hljs-comment"># define the folder path to where the csv files will be exported to</span>
folder_path = pl.Path(
    <span class="hljs-string">r"C:\Users\userName\OneDrive\csv"</span>
)

<span class="hljs-comment"># build connection string to the database</span>
conn = pyodbc.connect(
    <span class="hljs-string">"DRIVER={ODBC Driver 17 for SQL Server};"</span>
    <span class="hljs-string">"SERVER=localhost;"</span>
    <span class="hljs-string">"DATABASE=AdventureWorks2022;"</span>
    <span class="hljs-string">"Trusted_Connection=yes;"</span>
)

<span class="hljs-comment"># create cursor</span>
cursor = conn.cursor()

<span class="hljs-comment"># get all sql server table names</span>
qAllTables = <span class="hljs-string">"SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE='BASE TABLE'"</span>
table_names = pd.read_sql_query(qAllTables, conn)
print(<span class="hljs-string">f"table_names:<span class="hljs-subst">{table_names}</span>"</span>)

<span class="hljs-comment"># Loop over all tables, get the data with a SELECT *, put it into a df and</span>
<span class="hljs-comment"># then save the data to csv</span>

print(<span class="hljs-string">f"Number of tables: <span class="hljs-subst">{len(table_names)}</span>"</span>)

<span class="hljs-keyword">for</span> table <span class="hljs-keyword">in</span> table_names[<span class="hljs-string">"TABLE_NAME"</span>]:
    <span class="hljs-keyword">if</span> table == <span class="hljs-string">"sysdiagrams"</span> <span class="hljs-keyword">or</span> table == <span class="hljs-string">"DatabaseLog"</span>:  <span class="hljs-comment"># Skip system tables</span>
        <span class="hljs-keyword">continue</span>
    query = <span class="hljs-string">f"SELECT * FROM <span class="hljs-subst">{table}</span>"</span>
    df = pd.read_sql(query, conn)
    file_path = folder_path / <span class="hljs-string">f"<span class="hljs-subst">{table}</span>.csv"</span>
    print(<span class="hljs-string">f"Saving to: <span class="hljs-subst">{file_path}</span>"</span>)
    df.to_csv(file_path, index=<span class="hljs-literal">False</span>)

conn.close() <span class="hljs-comment"># close the connection, always a good move.</span>
</code></pre>
<p>I am assuming that you already have a Fabric capacity. If you don't you may be eligible for a <a target="_blank" href="https://learn.microsoft.com/en-us/fabric/get-started/fabric-trial">free trial</a>. In this case, you may want to follow the steps as described on Microsofts documentation website <a target="_blank" href="https://learn.microsoft.com/en-us/fabric/get-started/fabric-trial#start-the-fabric-capacity-trial">here</a>.</p>
<p>Next, go into your Fabric capacity. Here, you should create a new workspace and a new lakehouse:</p>
<p>Click on the "Create Workspace" button in the left menu bar (1) which opens a fold-out window. After this, click on the green "+ Create Workspace" button (2) in the bottom left corner.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1724367604236/cdc35ee3-112a-41fe-98da-2bef9af56a74.png" alt class="image--center mx-auto" /></p>
<ul>
<li>Next, on the right side, another flap-out window is opening. Here, you can enter your workspace name. The description is optional. If you can, I advise to use the trial capacity that may be available to you. For this click ont he radio button a bit further down in that very window. At the bottom of this very window, you can click on the green "Apply" button to create the workspace. The other options are optional and are not needed for this tutorial.</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1724367638294/e96a2e23-96de-40f9-822a-1a7cab75c90c.png" alt class="image--center mx-auto" /></p>
<p>Go into your workspace you just created. Click on "Workspaces" and you'll find the workspace that you've just created in the flap-out window on the left.</p>
<p>Next, click on the [+ New] button on the left top menu bar. This will open a drop-down window from where you'll be able to choose a new lakehouse. Click on the "Lakehouse" option.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1724367694774/3e502b78-8db7-479e-ac1e-a661b39220c4.png" alt class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1724367683152/2f265758-1e75-4625-8f55-1e91ff977eef.png" alt class="image--center mx-auto" /></p>
<p>Next, you'll want to enter your lakehouse name into the pop-up window that appeared. I recommend to use a name that is something like adventureworks_bronze. This makes it easier to understand what this lakehouse contains and that it is a bronze layer of the lakehouse. I'll be explaining the layer concept in a later blog post, but for now, we don't have to worry about that. If you have the option to click on "Lakehouse schemas" that is currently in public preview, I recommend not to choose this. The reason is that it can make the concept a bit more complicated which we don't need for this tutorial. In a later post, I'll cover this, but for now, let's skip this. Click on the green "Create" button to create the lakehouse.</p>
<p>Now that we've created it, we can upload our files. In the middle of the screen (1), we have the possibility to upload the files, but we also could make use of a right click on the folder "Files" (2) that you see on the left side of the screen. The advantage the second "method" is that you actually see the folder where your files will be uploaded to. You can create a folder in that folder if you want to. For this tutorial, i am going to keep this simple and will upload the files directly into this root folder. In the flap-out window on the right (3) you can go to your local folder and select all your files you've just created with the Python script above. If you choose the option "Overwrite if files already exist" you can upload the files again and overwrite the existing ones.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1724367774352/5a25e798-7359-410c-9db1-b2d07d97c830.png" alt class="image--center mx-auto" /></p>
<p>So this is for today, I'll be back with the next blog post on this soon!</p>
<p>Credit: Cover image was created by DALL-E via Microsofts CoPilot.</p>
]]></content:encoded></item><item><title><![CDATA[T SQL Invite December 2023]]></title><description><![CDATA[T-SQL Tuesday No. 169 – December 2023
I’ve got the honor to invite you to the blog party SQLTuesday, tagged as #tsql2sday, for this December 2023. Many thanks to Steve Jones (blog, twitter) for letting me host this month’s blog party!
December is the...]]></description><link>https://kayondata.com/t-sql-invite-december-2023</link><guid isPermaLink="true">https://kayondata.com/t-sql-invite-december-2023</guid><category><![CDATA[tsql-tuesday]]></category><category><![CDATA[T-SQL]]></category><dc:creator><![CDATA[Kay Sauter]]></dc:creator><pubDate>Sun, 03 Dec 2023 18:01:21 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1732648163781/732dffbd-491d-4fd5-b9e1-2cba3c519b93.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>T-SQL Tuesday No. 169 – December 2023
I’ve got the honor to invite you to the blog party <a target="_blank" href="https://tsqltuesday.com/">SQLTuesday</a>, tagged as #tsql2sday, for this December 2023. Many thanks to Steve Jones (<a target="_blank" href="https://voiceofthedba.com/">blog</a>, <a target="_blank" href="https://twitter.com/way0utwest">twitter</a>) for letting me host this month’s blog party!
December is the last month of a year. I believe December is the season of being thankful, or at least looking back. I also believe that we generally do not say “thank you” enough to people. So I invite and encourage you to say thank you to anyone who helped you:</p>
<ul>
<li>Work colleagues or employers</li>
<li>Mentors</li>
<li>Friends</li>
<li>Family
Your post does not have to be personal, it can also be just a story you anonymize people so that the reader who it actually matters, will know that you appreciate them. If you even can incorporate some code, please do! 
Happy blogging and happy SQLTuesday! Please publish your blog article on December 12 2023.</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Fabrics OneDrive]]></title><description><![CDATA[OneLake
Sorry, my title indeed said "OneDrive" and that is actually by intention. It is really easy to mix them up, just look at this screenshot I did on my Laptop. It shows the file explorer and it very much looks the same.

OneLakes logo is also sh...]]></description><link>https://kayondata.com/fabrics-onedrive</link><guid isPermaLink="true">https://kayondata.com/fabrics-onedrive</guid><category><![CDATA[microsoftfabric]]></category><category><![CDATA[microsoft fabric]]></category><category><![CDATA[OneDrive]]></category><category><![CDATA[one drive]]></category><category><![CDATA[onelake]]></category><dc:creator><![CDATA[Kay Sauter]]></dc:creator><pubDate>Mon, 21 Aug 2023 06:42:30 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1732646998054/97d9af65-2b9f-439f-a7fd-847b7999b63c.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-onelake">OneLake</h1>
<p>Sorry, my title indeed said "OneDrive" and that is actually by intention. It is really easy to mix them up, just look at this screenshot I did on my Laptop. It shows the file explorer and it very much looks the same.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1732647247431/11c3b9a5-372f-46b1-bed7-3a93b5387231.jpeg" alt class="image--center mx-auto" /></p>
<p>OneLakes logo is also shown in the taskbar in the bottom right corner next to the clock in Windows, just like OneDrive.</p>
<p>At this point, I want to mention it, since I've fogotten to mention it in the last post. Currently, Microsoft Fabric is still in preview.</p>
<p>So what is the big deal with OneLake? Well, if you are using Fabric, you'll notice that OneLake is pretty much the data center for any of Fabrics data experiences:</p>
<ul>
<li><p>Data Factory</p>
</li>
<li><p>Synapse Data Engineering</p>
</li>
<li><p>Synapse Data Warehouse</p>
</li>
<li><p>Synapse Data Science</p>
</li>
<li><p>Synapse Real Time Analytics</p>
</li>
<li><p>Power BI</p>
</li>
</ul>
<p>You could imagine this as a centre for all data. You want to provide data for Data Science? OneLake can provide you this. You also can use it for your DataLake to source your DWH in Fabric. Actually, you could imagine it as the center of Fabric.</p>
<h1 id="heading-onelake-shortcuts">OneLake Shortcuts</h1>
<p>Microsoft also provides <a target="_blank" href="https://learn.microsoft.com/en-us/fabric/onelake/onelake-shortcuts">OneDrive Shortcuts</a>. This is a Feature that allows you to unify your data in one place, hence avoiding duplication of your data. It is a virtualization of the data and you can access it from different experiences and places. In other words, you could imagine them as links that point to the actual places of data. I don't want to go much deeper in this, because the previous link to Microsofts documentation is excellent.</p>
<h1 id="heading-onelake-as-data-driver">OneLake as data driver</h1>
<p>A lot of companies strive to be a data driven company. Understandably so, because it means that decisions that need to be taken, be it as an analyst, data scientist, data engineer or anyone of the C-level management, will be based on data. OneLake gives transparency to everyone to the structure that data may provide and any data worker can incorporate them in their work.</p>
<p>So in my opinion, OneLake helps to centralize data to any organization. Architects and Security Engineers need to define together with the business and other data workers, how to organize the data system in the organization. Once this is done, and <a target="_blank" href="https://learn.microsoft.com/en-us/fabric/onelake/onelake-security">security permissions are set</a>, I believe, this will propel the mutual understanding of an organizations existing data across departements.</p>
<p>So this was about OneLake. In my next post, I will go deeper in Shortcuts to show you how to use them and to glide in to the next topic.</p>
<p>Caption for cover picture: OneLakes Logo by Microsoft</p>
]]></content:encoded></item><item><title><![CDATA[Getting into Fabric]]></title><description><![CDATA[Finally, I've found time to dig deeper into Microsoft Fabric
So finally I've found time to dig deeper into Microsoft Fabric, the newcomer in Microsofts data platform family.
I've played a bit with it and I thought I should blog about some distinct di...]]></description><link>https://kayondata.com/getting-into-fabric</link><guid isPermaLink="true">https://kayondata.com/getting-into-fabric</guid><category><![CDATA[microsoftfabric]]></category><category><![CDATA[microsoft fabric]]></category><dc:creator><![CDATA[Kay Sauter]]></dc:creator><pubDate>Mon, 14 Aug 2023 19:42:11 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1732647659526/2c169d95-ce45-4726-ae27-0462b17d868b.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-finally-ive-found-time-to-dig-deeper-into-microsoft-fabric">Finally, I've found time to dig deeper into Microsoft Fabric</h1>
<p>So finally I've found time to dig deeper into Microsoft Fabric, the newcomer in Microsofts data platform family.
I've played a bit with it and I thought I should blog about some distinct differences in between Azure Synapse Analytics and SQL Server. In the beginning, I have to admit, that MS Fabric quite confused me in terms of what it could mean for Azure Synapse.</p>
<p>Now that I've got more knowledge through reading into it and more importantly, playing around with it, I can draw some conclusions. But first and foremost, I think anyone should understand that MS Fabric is a SaaS offering, while Azure Synapse is a PaaS offering. This means that inherently, there are different approaches and use cases for both. </p>
<h1 id="heading-fabric-vs-synapse">Fabric vs Synapse?</h1>
<p>Anyways, I do not think that MS Fabric is going to replace Azure Synapse. I guess both will co-exist. Just like the way for example, Azure SQL and Azure SQL Managed instance co-exist. They both do not mean that they are going to replace SQL Server, neither will any of them replace each other. There are some hints for this, one being this video: {{&lt; youtube 6BI89Y2S-Oo &gt;}} 
This video deserves much more attention than it has (shout-out to <a target="_blank" href="https://www.linkedin.com/in/ginger-grant/">Ginger Grant</a> for pointing me to this video!). Also the channel needs much more attention than it does have!</p>
<p>As I am intending to start a small series about Fabric, I want to begin with some differences, as I mentioned in the beginning of the article. </p>
<h1 id="heading-roundup">Roundup</h1>
<ul>
<li>Fabric has in its core, center, and is functioning around one concept: OneLake. I believe this is the thing that makes the magic of all of it possible in the first place. All services around it, they work incredibly smoothly and well together. I really admire that concept.</li>
<li>OneLake inherently improves the concept of having a better convergence of people working with data, one and non-duplicated data. Think about this: Data Scientists, Data Engineers, and heck, Business Analysts can share their data in one place. One place to rule them all (data, not the people 😉). I think that is quite revolutionary if you think about the fact that most data today are pretty much scattered in data silos. Fabric is all about breaking up data silos. </li>
<li>While Fabric as it is today, does integrate many services existing today in one SaaS offering, it does not cover all abilities of each service. Nevertheless, it is a strong, powerful and very enjoyable tool to work with.</li>
</ul>
<p>So this is the start of the mini series in which I will blog about Fabric. I will try to keep it succinct as possible and blog what I've am learning about Fabric or Azure Synapse Analytics, to help you to understand the differences and similarities of them.</p>
]]></content:encoded></item><item><title><![CDATA[My week at SQLBits 2023: Why You Should send your Employees to Conferences]]></title><description><![CDATA[This post was first published on my LinkedIn Newsletter on 20 March 2023.

As I'm now tired, but happy and heading back on my bus of national express to Heathrow airport and taking a plane there back to my hometown Zurich, Switzerland, I'd like to wr...]]></description><link>https://kayondata.com/my-week-at-sqlbits-2023-why-you-should-send-your-employees-to-conferences</link><guid isPermaLink="true">https://kayondata.com/my-week-at-sqlbits-2023-why-you-should-send-your-employees-to-conferences</guid><category><![CDATA[sqlbits]]></category><category><![CDATA[conference]]></category><category><![CDATA[opinion]]></category><dc:creator><![CDATA[Kay Sauter]]></dc:creator><pubDate>Sun, 04 Jun 2023 13:48:46 GMT</pubDate><content:encoded><![CDATA[<blockquote>
<p>This post was first published on my LinkedIn Newsletter on 20 March 2023.</p>
</blockquote>
<p>As I'm now tired, but happy and heading back on my bus of national express to Heathrow airport and taking a plane there back to my hometown Zurich, Switzerland, I'd like to wrap up my days here in Wales for the extremely fantastic SQLBits conference. It was amazingly well organized, and I really hope I may return next year! I truly had a blast being here, while learning tons, meeting lots of people and last but not least, speaking here. So, I really want to say a huge thank you to everyone who were somehow involved with #sqlbits2023 that made it a such great success as it is! That includes not only the organizing team (Simon Sabin, Darren Green, Anette Allen (sorry, couldn't find you!), Jonathan Allen, Alex Whittles, Rob Sewell, Tracy and last but not least Sewell (sorry again!)). But this also includes the numerous amout of helpers, volunteers and all the sponsors of SQLBits. Last but not least, all the visitors that made the conference what it was - a truly extraordinary experience and incredibly thrilling, resourceful place to learn. For those who were not as lucky as I was, to be able to go to #SQLBits2023, I absolutely believe that employers should send their fair share of delegation of people to conferences like this. I often get to hear that "you can't measure the value of a conference like this". However, I am sure that many, many things that companies do and believe in, they can't be measured either. Yet, there is undeniably a benefit, for example having a marketing department. For a lot of campaigns, the direct benefit is hard to measure, and many companies choose not even bother to measure them properly, yet there is undeniably a benefit from it. The same is true for people who were being sent to conferences, for they not only learn more about stuff that they may face sooner or later. And it is work, too. Trust me, after the conference, I was worn out, I was tired, even though I had an exciting time. People may have fun at conferences and that is because they love their topic. But they not only learn more about solving problems, but also networking. Networking, talking to people with other insights and ideas, is extremely valuable. If you, as an employer, send your people and yes, paying them to do so, they feel valued, they feel appreciated and taken seriously. They will chat with other experts, and believe me, most of those well known people, MVPs, they do like to chat with others. They are there for exact this reason. Because IT is really a fast pacing world, and if you intend to keep your employees for a longer term, then you need to develop their skills and fantastic conferences like SQLBits do help tremendously. That being said, I want to list some encounters I've had, tell short stories about them and prove that it does make sense, having IT people to network outside their country, and yes, even outside of their IT areas and even to listen to stimulating, non-technical talks that can expand your social skills. Following, I'll name some incredible people I've met: Valerie Junk with whom I've I've enjoyed a great fruitous and interesting discussion over lunch, together with my friend Nikola Ilic. I hope I'll have the pleasure to meet both again in near future! No alt text provided for this image</p>
<p>André Kamman who's an incredible person and I would love to meet again! Unfortunately, we didn't have the beer together as i wished to, but lets do that another time! My friend Alpa Buddhabhatti who was truly inspiring! I hope you'll have huge success during your continuing speaking journey! I will definitively continue to watch you doing so! No alt text provided for this image From left to right: Tracy, Nikola, Adriano and I.</p>
<p>Adriano da Silva, who I've been friends with for years, but haven't had the chance to meet in person yet, but finally, we did last Tuesday night! Same is true for Tracy A. Boggiano, an amazing and fun person to be around with! We shall meet again online soon for our Virtual DEI Group we're leading together with our friend Deepthi Goguri. We'll also meet at #MVPSummit in Redmond this April! Craig Porteous, Ben Weissman, and William Durkin, great to meet and chat with you again! I hope we'll meet again, perhaps at this years DATA :Scotland? Cathrine Wilhelmsen, we unfortunately didn't have the opportunity to really chat together, my apologies for that! I hope you've loved the swiss chocolate that I could pass to you quickly, though! But perhaps we'll have another chance at the MVP Summit? By the way, I really loved your inspiring talk about pushing yourself outside of comfort zone to learn thing - an amazing presentation! I wish I was as talented as you for giving presentations! Gavita Regunath, Ph.D.👩🏽 🔬📚 whom I also would have loved to talk more, again, perhaps at the MVP Summit in April? Traci and Rob Sewell (sorry, Traci, no Link to you. Couldn't find you on LinkedIn) so amazing people, both of them. Not only having so big hearts and to give to the community, but also so incredibly kind, passionate and knowledgeable. I admire Mathias Thierbach for his sheer determination he must have brought up for publishing his amazing Tabular Definition Language! I've been blown away of your presentation together with Gabi Münster! No alt text provided for this image Picture of the presentation by Gabi and Mathias who will speak in Switzerland on April 6 at PBI User Group Switzerland (online)! Speaking of which, Gabi, Markus Ehrenmueller-Jensen, Denis Selimovic and Nikola Ilic joined up for a picture taken by Reitse Eskens (see topmost picture) - it was the first time ever that the first organization team of DATA BASH 2022 had an assembly of 5 people in one place. I haven't met both Markus and Gabi in person yet before this conference. I could go on much longer and tell you that those encounters were worth every penny I've spent in UK. However, instead of naming more people and putting sharing my experience with them, I'll just list them here. Some of them, I've just met, others, I have known for years, but online only, and again others have been friends for years also in real life. If I forget anyone, please apologize me for that, my mind has failed you in that case. I truly had a blast with all of you and hope to see you soon again or hope to chat with you either here on LinkedIn (PMs are welcome!) or through my other channels like Mastodon. Kristyna Hughes, Andrew Pruski, Reitse Eskens, Pragati Jain, Grace O'Halloran, Mathias Halkjær Petersen, Kurt Buhler, Cláudio Silva, Matt Gordon, Shaun Atkinson (sorry, couldn't find you on LinkedIn!), Johan Ludvig Brattås, Alex Whittles, Mara Pereira, Michael Johnson, Sander Stad, Dinakar Nethi, Nicky van Vroenhoven, Buck Woody, Taiob Ali, Stephanie Locke, Rodney Kidd, Deborah Melkin, Andy Yun, Heini Ilmarinen, Steve Jones, Alexander Klein, Erin Stellato, Kevin Chant, Gethyn Ellis, Justin Bird, Anthony Nocentino, Patrick Leblanc, Adam Saxton, Olivier Van Steenlandt and like I've said, many, many more that just don't cross my mind right now. Now, I am very excited to go home and I look forward to be back with my wife [who chose not to get tagged] again.</p>
<p>Commentary edit: this post was first posted on LinkedIn, and tags to many people listed here was done. This post is therefore <a target="_blank" href="https://www.linkedin.com/pulse/my-week-sqlbits-2023-kay-sauter/">best read on LinkedIn here</a>.</p>
<p>#conference #microsoft #sqlserver #sqlfamily #azure #azurefamily #powerbi #powerbifamily</p>
]]></content:encoded></item><item><title><![CDATA[T-SQL Tuesday #159]]></title><description><![CDATA[My friend, Deepthi Goguri, invited everyone to blog about two topics as a request for T-SQL Tuesday. T-SQL Tuesday is a once a month occurring event, a so-called blog party, for which someone invites others to blog about a topic. You can learn more a...]]></description><link>https://kayondata.com/t-sql-tuesday-159</link><guid isPermaLink="true">https://kayondata.com/t-sql-tuesday-159</guid><category><![CDATA[t-sql-tuesday]]></category><category><![CDATA[T-SQL]]></category><dc:creator><![CDATA[Kay Sauter]]></dc:creator><pubDate>Tue, 14 Feb 2023 10:45:22 GMT</pubDate><content:encoded><![CDATA[<p>My friend, Deepthi Goguri, <a target="_blank" href="https://dbanuggets.com/2023/02/05/t-sql-tuesday-159-invitation-whats-your-new-favorite-feature/">invited everyone to blog</a> about two topics as a request for T-SQL Tuesday. T-SQL Tuesday is a once a month occurring event, a so-called blog party, for which someone invites others to blog about a topic. You can learn more about this on this website of <a target="_blank" href="http://tsqltuesday.com/about/#:~:text=T-SQL%20Tuesday%20is%20the%20brainchild%20of%20Adam%20Machanic.,do%20so%20on%20that%20particular%20month%20or%20not.">tsqltuesday</a> itself.</p>
<p>Deepthi, aka dbanuggets on both <a target="_blank" href="https://tech.lgbt/@dbanuggets@techhub.social#">Mastodon</a> and <a target="_blank" href="https://twitter.com/dbanuggets?s=20">Twitter</a>, asked two to write about two topics, I quote her directly here:  </p>
<blockquote>
<ol>
<li>Blog about your new favorite feature in SQL Server 2022 or in Azure. Why is it your favorite feature and what are your experiences and learnings from exploring this feature? If you have not explored these new features yet, No worries! Blog about the features you feel interested in exploring.</li>
<li>New year, New Resolutions. What are your new year resolutions and how do you keep the discipline doing it day after day? Here are some examples: new hobby, plan to spend more time doing physical activity, wanted to read list of books (Please mention the names so it may also inspire others to read those books), journaling or any other resolutions you plan for this year.</li>
</ol>
</blockquote>
<p>So, without any further ado, I'm going to dive into those topics: </p>
<ol>
<li>With SQL Server Version 2022 (Build 16) Microsoft extended the list of its features again, as Microsoft documented it on the learn.microsoft.com-site <a target="_blank" href="https://learn.microsoft.com/en-us/sql/sql-server/what-s-new-in-sql-server-2022?view=sql-server-ver16">here</a>. </li>
</ol>
<p>Now, I've got two things that I am really excited about, one smaller but very handy feature: <a target="_blank" href="https://learn.microsoft.com/en-us/sql/t-sql/functions/string-split-transact-sql?view=sql-server-ver16">STRING_SPLIT()</a>. This is a very handy function that many developers have been waiting for years! With it, you finally can split strings like you can do it in PowerShell or Python, just to name two of a plethora of programming languages that have a similar function.</p>
<p>The second thing I am stoked about is the failover an on prem SQL Server 2022 into Azure Managed Instance! And you can do this back again! That means, potentially, you would not have the need to buy a new server first, but you just could use a Managed Instance momentarily and thus potentially cut costs! If you want to know more about it, I recommend to watch a <a target="_blank" href="https://youtu.be/ncF-zFzBDAY?t=269">video</a> on which <a target="_blank" href="https://www.linkedin.com/in/bobwardms/">Bob Ward</a> explains this. </p>
<ol start="2">
<li>Now, I don't do new year resolutions because I don't believe in them. However, I do set goals for myself, but they do not depend on a new year, but more a time span. I've got some projects in my pipeline and one of them will mean that I will try to blog more again this year and I hope to be ablet to attend some conferences in person. Last but not least, I am currently planning the next edition of <a target="_blank" href="https://databash.live/">DATA BASH</a> and that is as much as I can say currently.</li>
</ol>
<p>So what's your answer to this? If you've just realized that it is T-SQL Tuesday today and you haven't written anything yet, I'll tell you a small secret: you still can publish it until the end of month, nobody is gonna bite you. Of course, the sooner, the better, still 😉.</p>
<p>So long, until the next time!</p>
]]></content:encoded></item><item><title><![CDATA[Sql Tuesday: Azure Data Groups]]></title><description><![CDATA[Every second Tuesday of a month, there's a T-SQL-Tuesday. This is a blogging event that everyone can participate. The idea was created by Adam Mechanic, but nowadays, it's hosted by Steve Jones. 
For this month, Rie Merritt called for advices for run...]]></description><link>https://kayondata.com/sql-tuesday-azure-data-groups</link><guid isPermaLink="true">https://kayondata.com/sql-tuesday-azure-data-groups</guid><category><![CDATA[sql-tuesday]]></category><category><![CDATA[data community]]></category><category><![CDATA[Azure]]></category><category><![CDATA[SQL Server]]></category><category><![CDATA[azure data user group]]></category><dc:creator><![CDATA[Kay Sauter]]></dc:creator><pubDate>Sun, 20 Mar 2022 18:30:55 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1732646894148/3000daf1-52c9-451c-8baf-e0615dd94170.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Every second Tuesday of a month, there's a T-SQL-Tuesday. This is a blogging event that everyone can participate. <a target="_blank" href="http://tsqltuesday.com/about/">The idea was created by Adam Mechanic, but nowadays, it's hosted by Steve Jones</a>. </p>
<p>For this month, <a target="_blank" href="https://techcommunity.microsoft.com/t5/azure-sql-blog/t-sql-tuesday-advice-on-running-a-user-group/ba-p/3213699">Rie Merritt called for advices for running a user group</a>.
I know, I'm a quite late to join this game. But since I was busy for my university just until this week, I was not able to write for the call more timely - apologies. I hope nobody minds me joining this late.</p>
<p>So I've founded an online-only Azure Data Community user group, <a target="_blank" href="https://www.meetup.com/data-tgif/">Data TGIF</a>, in June last year and have led it ever since. It is still fairly small and only has around 170 members so far, so it is up to you decide whether or not this group is successful.</p>
<p>In my opinion, an important factor for a user group is to set yourself apart from others. There are currently <a target="_blank" href="https://www.meetup.com/pro/azuredatatechgroups">140 Azure Data Tech Groups in 40 countries</a> worldwide, so especially if your user group are online events, you've got some competition. Get a catchy name and a logo that is unique. My group is called <a target="_blank" href="https://www.meetup.com/data-tgif/">Data TGIF</a> and unlike most other groups, it does not just offer a platform for speakers to speak, but it is also a place to meet others. So it is also an networking event. I'm very happy to see that that way, that this group is able to spark talks amongst the attendees and speakers. Oftentimes, this breaks the ice for everyone. And during those talks, oftentimes new questions pop up and get asked to the speakers. Another thing this user group is set apart from others with is the format: it is 30 mins for talks and 30 mins for the networking part. That way, so I like to believe, there is a good balance in between just listening and interaction of every attendee. </p>
<p>I am vehemently believing that user groups are a great way to start speaking. So if anyone wants to try to speak, I am more than happy to help and if the speaker wishes, I will try to find a mentor for them. User Groups should be a speaking platform for everyone, from the freshest speakers to the most experienced speakers. </p>
<p>Furthermore, try to find channels you're communicating and stick to them. For Data TGIF, I am using 3 channels: <a target="_blank" href="https://www.meetup.com/data-tgif/">MeetUp.com</a>, <a target="_blank" href="https://twitter.com/DataTgif">Twitter</a> and <a target="_blank" href="https://www.linkedin.com/company/75026820/">LinkedIn</a>. Be consistent in using them and try to get as many connections as you can. Get in touch with the speakers and provide them marketing materials so that they can do marketing themselves, too.</p>
<p>Last not least, try to get in touch with the community. So far, I haven't seen anyone of the other community founders or leaders biting me. In fact, I can say that I've grown to be friends with some of them and with some of them, I even have plans for future events. Some other user group organizers will even help you out with growing your user group - a shoutout to <a target="_blank" href="https://www.meetup.com/cloud-data-driven/">Cloud Data Driven</a> for this!</p>
<p>Good luck in growing your user group and of course, don't forget the most important part in this: have fun!</p>
]]></content:encoded></item><item><title><![CDATA[Bye-bye 2021]]></title><description><![CDATA[Looking back
So the year 2021 is history. For me, it was a quite eventful year although exactly one year ago, I hoped that the pandemic situation would improve. It kind of did, true. However I hoped it'd be better than it turned out to be. Neverthele...]]></description><link>https://kayondata.com/bye-bye-2021</link><guid isPermaLink="true">https://kayondata.com/bye-bye-2021</guid><category><![CDATA[New year]]></category><category><![CDATA[Editorial]]></category><dc:creator><![CDATA[Kay Sauter]]></dc:creator><pubDate>Mon, 03 Jan 2022 13:37:02 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1732646339730/9bc0d62f-4c1a-4a2d-8356-b0a9945ade24.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1 id="heading-looking-back">Looking back</h1>
<p>So the year 2021 is history. For me, it was a quite eventful year although exactly one year ago, I hoped that the pandemic situation would improve. It kind of did, true. However I hoped it'd be better than it turned out to be. Nevertheless, I hope on January 1st 2023, I'll have a better lookback to the year 2022 regarding this. </p>
<p>In March 2021, I've submitted my thesis for <a target="_blank" href="https://fh-hwz.ch/produkt/cas-machine-learning/">CAS Machine Learning (German)</a> at my <a target="_blank" href="https://fh-hwz.ch/english/">University of Applied Sciences in Business Administration Zurich, HWZ</a>. It got accepted, so I received that first CAS diploma of three.</p>
<p>After I've decided to volunteer to moderate some sessions for the both DataWeekenders in 2020, I wanted more. Originally, I planned to start to speak in 2022 after I've finished my studies at University for Applied Sciences HWZ in Zurich where I am pursuing a MAS in Business Intelligence (planning to finish in December 2022). But I got hooked, badly. So I was looking around what it takes to be speaking and also looked for a mentor who I found in <a target="_blank" href="https://www.dataweekender.com/">Sander Stad</a>. Thank you a lot again, <a target="_blank" href="https://www.sqlstad.nl/author/sander/">Sander</a>, for your tips and encouragements you gave to me! One of my highlights was truly my speaking debut on <a target="_blank" href="https://www.dataweekender.com/">DataWeekender</a> where I also volunteered to be a moderator again. I really am looking forward to be moderating and hopefully speaking on this event again! Since, I got the opportunity to speak on some other conferences and user groups so that actually became a new hobby of mine. Instead of listing all opportunities to speak here, you'll find <a target="_blank" href="https://www.kayondata.com/speaking/">on this site</a>. </p>
<p>In June, I've created the <a target="_blank" href="https://www.meetup.com/data-tgif/">Azure Data Community User Group, Data TGIF</a>. This User Group has an event every month where someone is presenting a session for 30 mins. After the session, the attendees and the speakers have the opportunity to talk to each other and just to do some networking. I'm always excited when we have attendees to meet and talk each others - from around the world. This small event really makes the "SQL Family" closer to a "world village community" and closes a gap many of us feel due to the pandemic.</p>
<p>In November, I've submitted my thesis for <a target="_blank" href="https://fh-hwz.ch/produkt/cas-customer-intelligence/">CAS Customer Intelligence (German)</a> at my university, received my second CAS diploma in December and shortly after the submission in November, I was given another big opportunity: actually co-organize a whole 24-hours long conference, the <a target="_blank" href="https://powerbifest.com/">Power BI Fest 2021</a>. It was quite a lot of work and I had fun while working on it.</p>
<h1 id="heading-outlook">Outlook</h1>
<p>If you know me, then you probably know that I'm always having some new projects up my sleeve. I'll tell about one or another soon! I am also working on some new sessions I hope to be select to talk about in this year.</p>
<p>For March 2022, I am looking forward to submit my last CAS thesis in <a target="_blank" href="https://fh-hwz.ch/produkt/cas-applied-data-analytics/">CAS Applied Data Analytics (German)</a> on which I'm currently working on. </p>
<p>In December 2022, I'm hoping to finish my studies at my University. </p>
<h1 id="heading-thanks">Thanks</h1>
<p>I just want thank all people from the SQL Community here who I've met, befriended, had nice conversations or just gave encouragements to me in the last year. Thank you all for your support and I really hope to see you all not just online, but also in person someday! I am optimistic that I will meet some other, new awesome people of the SQL Family and am looking forward to do so! </p>
<p>Image credit: Image by Moritz Knöringer via Unsplash.</p>
]]></content:encoded></item><item><title><![CDATA[Speaking Engagements September 2021]]></title><description><![CDATA[This post was originally published on my employers blog first.
September marks the end of this summer but we've got some exciting news about new events that we'll be joining this month. This post is giving you an overview over these!
We hope you've e...]]></description><link>https://kayondata.com/speaking-engagements-september-2021</link><guid isPermaLink="true">https://kayondata.com/speaking-engagements-september-2021</guid><category><![CDATA[speaking ]]></category><category><![CDATA[Community Engagement]]></category><dc:creator><![CDATA[Kay Sauter]]></dc:creator><pubDate>Mon, 06 Sep 2021 07:50:41 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1732493981228/3d03acba-304c-427a-b084-e8664672de37.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>This post was originally published on <a target="_blank" href="https://www.onedigit.ch/details/end-of-summer-break-and-upcoming-events">my employers blog first</a>.</p>
<p>September marks the end of this summer but we've got some exciting news about new events that we'll be joining this month. This post is giving you an overview over these!</p>
<p>We hope you've enjoyed your summer break and are ready for the new season for many new events!</p>
<p>This September '21, I'll have two very interesting events that I will attend as a speaker, panel member or a moderator apart from organizing Data TGIF this Friday: </p>
<p><a target="_blank" href="https://www.meetup.com/data-tgif/events/278829747/">Data TGIF</a>
This month, we'll have a full hour talk given by the outstanding speaker Tracy Boggiano and I'm very much looking forward to listen to her about Mental Health and Wellness in IT: Safeguarding our most precious resource at the Data TGIF user group I'm organizing! The event is this Friday at 15:30 CEST.</p>
<p><a target="_blank" href="https://dataplatformgeeks.com/dps2021/">Data Platform Summit 2021</a>
This is one of the major SQL Server / Azure SQL / Power BI events worldwide that will be held from September 13-16 around the clock to serve all timezones with having more than 100 sessions. It also features a pre- (8+9 Sept) and post-conference (20+21 Sept). I am going to speak about Automated provisioning of SQL Server VMs on Azure on Sept 13 at 18:15-19:30 CEST where you will be able to chat with me live. 
You can sign up for this event here. I'm very honored to be a member of the highlighted panel discussion about Diversity, Equity Inclusion on Sept 15 17:30-18:45 CEST. Finally, I'll also be available for a chat-session on Sept 16 at 14:30 CEST.</p>
<p><a target="_blank" href="https://datadrivencommunity.com/">Future Data Driven Summit</a>
is a new event that will be held for the first time. It will be a full day event with different types of topics related to Data &amp; AI, DevOps, PowerBI and Data Visualization, Integration &amp; Automation and cloud infrastructure. I am honored to be holding a keynote and moderating for the Future Data Driven Summit which will be on September 29 at 16:00 CEST and has a very intriguing speaker lineup! You can join this event for free here. At the moment, we have more than 1200 people that have signed up!</p>
<p>I hope you'll be joining in one or even all of my sessions I'll be organizing, giving or moderating!
Like always, if you have any questions, feel free to contact me via my <a target="_blank" href="/contact">contact page</a>!</p>
]]></content:encoded></item><item><title><![CDATA[Why We Like to Recommend Dbatools]]></title><description><![CDATA[This post was first published for my employer, OneDigit AG, here.
dbatools really has the ability to change a DBAs work life. It is a free, very powerful and feature rich open source PowerShell module that offers you 550+ commands that allows you to ...]]></description><link>https://kayondata.com/why-we-like-to-recommend-dbatools</link><guid isPermaLink="true">https://kayondata.com/why-we-like-to-recommend-dbatools</guid><category><![CDATA[dbatools]]></category><category><![CDATA[dba]]></category><category><![CDATA[SQL Server]]></category><category><![CDATA[administration]]></category><category><![CDATA[Powershell]]></category><category><![CDATA[PowerShell Automation]]></category><dc:creator><![CDATA[Kay Sauter]]></dc:creator><pubDate>Wed, 07 Jul 2021 15:36:20 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1732646095847/e69868da-57ac-410e-b47d-e1d68de61dd0.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>This post was first published for my employer, OneDigit AG, <a target="_blank" href="https://www.onedigit.ch/details/why-we-recommend-dbatools">here</a>.</p>
<p><a target="_blank" href="https://dbatools.io/">dbatools</a> really has the ability to change a DBAs work life. It is a free, very powerful and feature rich open source PowerShell module that offers you 550+ commands that allows you to take backups, restore them or just do a lot in SQL Server administration that you would need SSMS and some work for. With dbatools, you can do many tasks more and that with just a few lines of code. This post gives you an introduction to dbatools.
dbatools is often called the SQL Server Management Studio (SSMS) for PowerShell. So in short, you can manage a SQL Server instance and its databases with PowerShell which also means that you can use scripts to manage them. Just to give some examples on how dbatools can be used so you’ll get a picture how helpful it can be.</p>
<pre><code class="lang-PowerShell"><span class="hljs-comment"># Simple query with a parameter which could be passed from a variable in the PowerShell code. It also prevents SQL injection.</span>
<span class="hljs-built_in">Invoke-DbaQuery</span> <span class="hljs-literal">-SqlInstance</span> <span class="hljs-string">"serverName"</span> <span class="hljs-literal">-Database</span> <span class="hljs-string">"dbName"</span> <span class="hljs-literal">-Query</span> <span class="hljs-string">'SELECT * FROM Employees WHERE Name = @name'</span> <span class="hljs-literal">-SqlParameters</span> <span class="hljs-selector-tag">@</span>{ Name = <span class="hljs-string">"Marc Linder"</span> }

<span class="hljs-comment"># Another example with multiple parameters, which could be assigned from variables in PowerShell code before, too.</span>
<span class="hljs-variable">$queryBetween</span> = <span class="hljs-selector-tag">@</span>{
    <span class="hljs-string">"StartDate"</span> = <span class="hljs-variable">$startdate</span>;
    <span class="hljs-string">"EndDate"</span>   = <span class="hljs-variable">$enddate</span>;
};
<span class="hljs-built_in">Invoke-DbaQuery</span> <span class="hljs-literal">-SqlInstance</span> <span class="hljs-string">"serverName"</span> <span class="hljs-literal">-Database</span> <span class="hljs-string">"dbName"</span> <span class="hljs-literal">-Query</span> <span class="hljs-string">"StoredProcedure_with_in_between_search"</span> <span class="hljs-literal">-SqlParameters</span> <span class="hljs-variable">$QueryParameters</span> <span class="hljs-literal">-CommandType</span> StoredProcedure

<span class="hljs-comment"># Creates full backups of the two databases dbName1 and dbName2. If you don't specify any database names, it backups all databases on the instance!</span>
<span class="hljs-comment"># Backups will stored in the default backup folder. You can specify the target folder with -Path though. With -Type Diff you also could choose a differential backup </span>
<span class="hljs-built_in">Backup-DbaDatabase</span> <span class="hljs-literal">-SqlInstance</span> <span class="hljs-string">"ServerName"</span> <span class="hljs-literal">-Database</span> dbName1, dbName2 <span class="hljs-literal">-Path</span> C:\backups\
</code></pre>
<p>Very simple, right? And there are 550+ commands like this in total that dbatools provides. Dbatools was invented by Chrissy LeMaire who is a MVP for Cloud and Datacenter Management &amp; also MVP for Data Platform. She develops and maintains it now with Jess Pomfret (MVP Cloud and Datacenter Management) and Shawn Melton (MVP Cloud and Datacenter Management) and with many other contributors.</p>
<p>In my experience, dbatools does not only an awesome job for a lot of tasks around SQL Server but is also relatively easy and fast to develop with. It is ideal for almost any problem you may encounter as a DBA. dbatools will truly change your DBA life! With dbatools you don’t develop your hardest parts of the scripts for a DBA problem alone anymore as dbatools is open source (see <a target="_blank" href="https://github.com/sqlcollaborative/dbatools">code on github here</a>) and thus tested within the community. A few lines of codes will boost your scripts that you would not be able to write alone.</p>
<p>If you’re worried about security, Chrissy also wrote <a target="_blank" href="https://dbatools.io/secure/">a very insightful post on dbatools.io</a> on how secure dbatools is – spoiler: its very secure.</p>
<p>So if you want to use dbatools, you can for example, just use SQL Server Job Agent which allows you to start your script according your needs, e.g. after a load or taking backups at a past midnight time very easily. Of course, you also can commit the code into a git repo and use it with SQL Server Job.</p>
<p>If you like to know more about dbatools, I recommend to <a target="_blank" href="https://www.manning.com/books/learn-dbatools-in-a-month-of-lunches?query=dbatools">buy and read the book</a> “Learn dbatools in a Month of Lunches” by <a target="_blank" href="https://blog.netnerds.net/resume/">Chrissy LeMaire</a>, <a target="_blank" href="https://blog.robsewell.com/about/">Rob Sewell</a>, <a target="_blank" href="https://jesspomfret.com/">Jess Pomfret</a> and <a target="_blank" href="https://claudioessilva.eu/about/">Cláudio Silva</a>.</p>
<pre><code class="lang-PowerShell"><span class="hljs-comment"># How to install dbatools:</span>
<span class="hljs-comment"># Via PowerShell Gallery (Win 10+ / Win Server 2016+)</span>
<span class="hljs-built_in">Install-Module</span> dbatools
<span class="hljs-comment"># Via Choclatey:</span>
choco install dbatools <span class="hljs-literal">-y</span>
</code></pre>
<p>More install options are described here and there is also an option with downloading it if your server does not have any internet connection.</p>
<p>If you want to update dbatools, it of course also has a very neat tool to update itself:</p>
<pre><code class="lang-PowerShell"><span class="hljs-built_in">Update-dbatools</span>
</code></pre>
<p>If you need to choose the download option, it may happen that after you’ve installed a newer version of dbatools, you’ll end up having more than just one version of dbatools. In that case, you may want to remove older versions. To do that, you can use the following code as documented here:</p>
<pre><code class="lang-PowerShell">&amp; <span class="hljs-string">"<span class="hljs-variable">$</span>((Get-InstalledModule dbatools).InstalledLocation)\cleanup.ps1"</span>
</code></pre>
<p>If you have any questions about dbatools, please feel free contact me. I’m happy to answer you!</p>
]]></content:encoded></item><item><title><![CDATA[Starting DataTGIF]]></title><description><![CDATA[This post was first published on my employers website/blog here.
I just started to organize a free to join MeetUp called Data TGIF that will be scheduled once a month on a Friday afternoon. This MeetUp organizes free data events for Friday afternoons...]]></description><link>https://kayondata.com/starting-datatgif</link><guid isPermaLink="true">https://kayondata.com/starting-datatgif</guid><category><![CDATA[data-tgif]]></category><category><![CDATA[azure data user group]]></category><category><![CDATA[user group]]></category><dc:creator><![CDATA[Kay Sauter]]></dc:creator><pubDate>Thu, 17 Jun 2021 19:56:47 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1732646019985/9163e64f-de7d-468e-81e1-fb566978fb9c.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>This post was first published on my employers website/blog <a target="_blank" href="https://www.onedigit.ch/details/first-data-tgif-event-up-on-june-25-2021">here</a>.</p>
<p>I just started to organize a free to join <a target="_blank" href="https://www.meetup.com/de-DE/data-tgif/">MeetUp called Data TGIF</a> that will be scheduled once a month on a Friday afternoon. This MeetUp organizes free data events for Friday afternoons to celebrate the start of the weekend by meeting people to a Microsoft Data Platform talk. If you don’t know the term TGIF: According to Wikipedia, <a target="_blank" href="https://en.wikipedia.org/wiki/Thank_God_It%27s_Friday">TGIF stands for Thank God It's Friday</a> and is a common expression in English speaking Western countries. It expresses gratification that the working week is nearly over, and a weekend of leisure will soon be here.</p>
<p>The goal of this group is to spread knowledge in Microsoft Data Platform and to give opportunities for national and international networking to the Swiss community. Primarily, this MeetUp will be held online so that everyone can participate from their own place.  As soon as the pandemic situation will improve, we will probably start to organize in-person meetings. Meetings are held in English so that we may cover all Swiss language regions.</p>
<p>The speaker usually will have 30 mins for their presentation and there will be 30-45 mins for networking afterwards although there may be exceptions to this rule. If we host an in-person event, it may have a different format than this.</p>
<p>The first speaker will be <a target="_blank" href="https://www.datahai.co.uk/about/">Andy Cutler</a> who is an Azure Data Platform professional working predominantly with Azure Synapse Analytics (SQL Pools), Data Factory, SQL Server and Power BI. He holds an MSc in Business Intelligence and Data Mining.</p>
<p>Andy will be speaking about <strong>Creating a Logical Data Warehouse using Azure Synapse Analytics Serverless SQL Pools</strong>.</p>
<p>We are looking forward to this event and would be happy to see you there! Please come and <a target="_blank" href="https://www.meetup.com/de-DE/data-tgif/events/278656966/">sign up for Andys session for free</a>!</p>
]]></content:encoded></item><item><title><![CDATA[Moving to Hugo]]></title><description><![CDATA[I've moved from WordPress to Hugo. Just because I've never really made very good experience with WordPress. Yes, it is easy, but one has just to hope that all plugins will work together and keeps working together in the future. My move to Hugo was in...]]></description><link>https://kayondata.com/moving-to-hugo</link><guid isPermaLink="true">https://kayondata.com/moving-to-hugo</guid><category><![CDATA[Editorial]]></category><category><![CDATA[cms]]></category><category><![CDATA[Hugo]]></category><dc:creator><![CDATA[Kay Sauter]]></dc:creator><pubDate>Sat, 12 Jun 2021 17:20:57 GMT</pubDate><content:encoded><![CDATA[<p>I've moved from WordPress to Hugo. Just because I've never really made very good experience with WordPress. Yes, it is easy, but one has just to hope that all plugins will work together and keeps working together in the future. My move to Hugo was inspired by <a target="_blank" href="https://www.littlekendra.com/2021/05/03/moving-from-wordpress-to-an-azure-static-site-with-hugo/">Kendra</a> and <a target="_blank" href="https://www.justinjbird.me/2021/learning-to-hugo-hello-world/">Justin</a>. Furthermore, the integral usage of Markdown makes it super easy to use code within a blog post. </p>
<p>At the moment, it is not possible to comment any posts yet. However, I am going to set it up in the next few weeks. I am planning to set it up with Gitalk, a commenting system that uses Git so that anyone who wants to comment a post needs to have an account. Since my blog targets a quite technical audience, this should not be an obstacle. If you're keen to contact me, you may do so by sending me a message on Linkedin, my page there is https://www.linkedin.com/in/kaysauter/.</p>
]]></content:encoded></item><item><title><![CDATA[SQL Server Management Studio and Azure Data Studio]]></title><description><![CDATA[If you’ve installed SQL Server Management Studio (SSMS) recently, chances are that you’ve noticed that Azure Data Studio (ADS) was installed too. Since SSMS Version 18.7, ADS gets installed alongside with SSMS by default. But you might not be familia...]]></description><link>https://kayondata.com/sql-server-management-studio-and-azure-data-studio</link><guid isPermaLink="true">https://kayondata.com/sql-server-management-studio-and-azure-data-studio</guid><category><![CDATA[SSMS]]></category><category><![CDATA[Azure Data Studio]]></category><dc:creator><![CDATA[Kay Sauter]]></dc:creator><pubDate>Wed, 02 Jun 2021 22:58:18 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1732494120937/d5cfff6a-92e7-4719-879e-af024ae32556.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>If you’ve installed SQL Server Management Studio (SSMS) recently, chances are that you’ve noticed that Azure Data Studio (ADS) was installed too. Since SSMS Version 18.7, ADS gets installed alongside with SSMS by default. But you might not be familiar with ADS. What is it and how does it set apart from SSMS? And how to install SSMS alone?</p>
<h2 id="heading-introduction">Introduction</h2>
<p>ADS is a free, <a target="_blank" href="https://github.com/microsoft/azuredatastudio">open source</a> and cross-platform SQL code editor with built-in IntelliSense that is also able to manage SQL Server in Azure, so just this description alone sets it quite apart from SSMS. And yes indeed, it is cross platform which means you can use it on Mac OS and on Linux.</p>
<h2 id="heading-features-that-ssms-does-not-have">Features that SSMS does not have</h2>
<p>ADS boasts some features that SSMS doesn’t have. Most important examples are:</p>
<p>Extensions ecosystem similar to that one like Visual Studio Code
Source control for git out of the box
Jupyter Notebooks for SQL, PowerShell, Python and more
Dashboard for an excellent overview over your Instance
Integrated terminal
Excellent in connecting to Azure
Dark mode
ADS is builds upon the same system as Visual Studio Code (VSCode). Don’t mix it up with Visual Studio – VSCode is an very extensible and powerful editor whereas Visual Studio is a fully fledged IDE. However, if you want, you can use extensions to make VSCode to a very powerful system that is close to an IDE or even meets it. If you’re familiar with VSCode,  you’ll be able to adapt to ADS very quickly.</p>
<h2 id="heading-so-why-still-use-ssms">So why still use SSMS?</h2>
<p>Well in short, SSMS still has some capabilities that ADS does not have, especially for administering SQL Server Instance. However, for most if not all SQL developments, ADS can do the same as SSMS or even better. When it comes to administering SQL Server though, SSMS is still more powerful. For example, if you want to configure Always On Groups, using Maintenance Plans, PolyBase, or Replication, SSMS is still the winner. Microsoft says that ADS does not replace SSMS. In my opinion, perhaps it will one day, but for the foreseeable future, it certainly does not yet.</p>
<h2 id="heading-but-can-i-avoid-installing-ads-when-installing-ssms">But can I avoid installing ADS when installing SSMS?</h2>
<p>Firstly, I personally advise to install ADS as well in most DEV environments because it does have some very interesting and useful features. But to answer this question, if you’re installing it via graphical user interface, then you are unfortunately forced to install ADS, too. If you must avoid the installation of ADS, for example because you want to have a hardened PRD, you can avoid it if you install it via command line this way:</p>
<pre><code class="lang-PowerShell">SSMS<span class="hljs-literal">-Setup</span><span class="hljs-literal">-ENU</span>.exe /Passive DoNotInstallAzureDataStudio=<span class="hljs-number">1</span>
</code></pre>
<p>If I uninstall SSMS, will ADS be uninstalled too?
No. If you want to uninstall both, you’ll have to uninstall them separately.</p>
<p>This was a short introduction in this topic. If you have more questions, we will be happy to answer hem!</p>
]]></content:encoded></item><item><title><![CDATA[My journey to my first speaking]]></title><description><![CDATA[To give a speaking in public for the first time is always scary. At the DataWeekender in October last year, Angela Henry who was moderating some sessions together with me, mentioned that there is a website where you can look for mentors and recommend...]]></description><link>https://kayondata.com/my-journey-to-my-first-speaking</link><guid isPermaLink="true">https://kayondata.com/my-journey-to-my-first-speaking</guid><category><![CDATA[speaking ]]></category><dc:creator><![CDATA[Kay Sauter]]></dc:creator><pubDate>Wed, 19 May 2021 10:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1732493752679/f17fa1f9-f8dc-490a-8fdc-f7baf4220666.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>To give a speaking in public for the first time is always scary. At the DataWeekender in October last year, Angela Henry who was moderating some sessions together with me, mentioned that there is a website where you can look for mentors and recommended it to me if I would want to pick up speaking. The website is <a target="_blank" href="http://www.speakingmentors.com">speakingmentors.com</a> and was created by <a target="_blank" href="http://workingwithdevs.com/">Alex Yates</a>, so I’ve later learned.</p>
<p>At the end of March this year, I’ve decided that I want to start to speak this year. Originally, I’ve planned to start to do so after my studies for Master of Advanced Studies in Business Intelligence at the <a target="_blank" href="https://fh-hwz.ch/english/">University of Applied Sciences in Business Administration Zurich</a>. However, I’ve been wanting to start speaking for a longer time, so I’ve just gave it a go after I would finish my first part of this studies for which I received the Certificate of Advanced Studies in Machine Learning just recently. If everything goes as planned, I’ll be finishing my studies in spring 2022.</p>
<p>Originally, I’ve planned to start at a <a target="_blank" href="https://www.newstarsofdata.com/">New Stars Of Data</a> event in 2022 and this spring, it actually fell in the time when I had to write the thesis for the CAS Machine Learning so that was not an option. But then there was <a target="_blank" href="https://www.dataweekender.com/">DataWeekender</a> for which I decided to apply to speak for.</p>
<p>So I went to the website for looking a mentor to get me some insights on how one should prepare and deliver a speaking. Through <a target="_blank" href="http://www.speakingmentors.com">speakingmentors.com</a>, I quickly found out that <a target="_blank" href="https://www.sqlstad.nl/">Sander Stad</a> specializes more or less the same topic I’ve decided to talk about and contacted him.  Sander is a very experienced speaker and did give me more than a few tips and tricks on how to improve a talk. Angela was right: <a target="_blank" href="http://www.speakingmentors.com">speakingmentors.com</a> is a very helpful resource you should turn to if you’re deciding to speak for the first time.</p>
<p>I would like to thank some people who gave me their support in this little journey:</p>
<ul>
<li>My employer <a target="_blank" href="https://www.onedigit.ch/">OneDigit AG</a> who supported me to start speaking on events</li>
<li>My wife Vivien who supported me by supporting me emotionally and giving me the time to prepare my speaking.</li>
<li><a target="_blank" href="https://www.sqlstad.nl/">Sander Stad</a> for his mentorship, advices and encouragements from his enormous experience he gave me. It was my honor to be your mentee, Sander! And I might add, we actually had some very nice conversations together! I'll owe you more than just a beer!</li>
<li><a target="_blank" href="https://sqlswimmer.com/">Angela Henry</a> for casually pointing me to speakingmentors.com and sparked the idea in me in the first place. Thank you a lot for that!</li>
<li><a target="_blank" href="http://workingwithdevs.com/">Alex Yates</a> for having the idea and actually creating this website with which you may find your future mentor. A nice and funny guy whom I've had the chance to meet over MS Teams once. Thank you!</li>
<li>The guys behind <a target="_blank" href="https://www.dataweekender.com/">DataWeekender</a>: <a target="_blank" href="https://twitter.com/kevchant">Kevin Chant</a>, <a target="_blank" href="https://twitter.com/gethyn_ellis">Gethyn Ellis</a>, <a target="_blank" href="https://twitter.com/markdataguy">Mark Hayes</a> and <a target="_blank" href="https://twitter.com/matesic_damir">Damir Matešić</a> who gave my the first opportunity to speak publicly. That was a great experience and your event will always be specially remembered!</li>
<li><a target="_blank" href="https://data-mozart.com/about/">Nikola Illic</a> who was encouraging me for speaking almost until I had to switch my microphone on. Thanks!</li>
<li>A huge thanks to the audience I’ve had - I was very nervous, but I hope that wasn't too noticeable!</li>
</ul>
<p>Thank you so much to everybody! Without you, my journey wouldn’t had been half fun! I am still learning and will be continuing my journey. And Alex, should I once feel that I’ve got enough experience, I’ll walk on Sanders path and sign up to your site to be a mentor for a next new speaker.</p>
<p>PS: If you’re looking for my DataWeekender session files, <a target="_blank" href="https://github.com/kaysauter/Sessions/tree/main/DataWeekender-15-May-2021">they’re over here</a>.</p>
<p>Photo by <a target="_blank" href="https://unsplash.com/@howier?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Howie R</a> on <a target="_blank" href="https://unsplash.com/s/photos/thank-you?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></p>
]]></content:encoded></item><item><title><![CDATA[Deploy Azure Sql Server Vm With PoSh]]></title><description><![CDATA[This post was first published on my past employers blog here. I thank OneDigit AG for the permission to publish my article also here. 
Especially in this time of COVID-19 caused lockdowns, working from home is widespread. Therefore, working together ...]]></description><link>https://kayondata.com/deploy-azure-sql-server-vm-with-posh</link><guid isPermaLink="true">https://kayondata.com/deploy-azure-sql-server-vm-with-posh</guid><category><![CDATA[Powershell]]></category><category><![CDATA[Azure]]></category><category><![CDATA[vm]]></category><category><![CDATA[Devops]]></category><category><![CDATA[automation]]></category><category><![CDATA[SQL Server]]></category><dc:creator><![CDATA[Kay Sauter]]></dc:creator><pubDate>Mon, 18 Jan 2021 13:24:51 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1732372882326/0d75d4c4-b869-4f8e-ad72-b7d0eaac4627.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>This post was first published on my past employers blog <a target="_blank" href="https://azure.microsoft.com/en-us/features/storage-explorer/">here</a>. I thank OneDigit AG for the permission to publish my article also here. </p>
<p>Especially in this time of COVID-19 caused lockdowns, working from home is widespread. Therefore, working together on a developing VM in the cloud may be an attractive solution. In the past few months, I developed a solution to use VMs for such business cases. We also had in our minds that this VM could be a showcase for customers that prefer having their solution on their premises without Azure. Therefore, a VM in Azure in a Infrastructure-as-a-Service (IaaS) solution can make a lot of sense. This can also be a cost-efficient solution for those who want to try out things for a couple of hours. Even if you need this VM for a longer time, the costs are fairly limited, depending on the hardware specifications you are choosing.</p>
<p>The following article is split in three parts. In the first part, a script sets up a VM from scratch. The second walks you through the code and will setup Azure Bastion. And the last part will walk you through the installation of some software including Azure Data Studio via Chocolatey and the restore of a sample database with dbatools.</p>
<p>The full setup takes a bit more than 1 hour and is set up almost completely automatically.</p>
<h1 id="heading-pre-requisites">Pre-requisites</h1>
<p>Editor and connection to Azure
I am using Visual Studio Code with some extensions for Azure. In order to connect to your Azure Account, you need to connect your editor of your choice with <a target="_blank" href="https://docs.microsoft.com/en-us/powershell/module/az.accounts/connect-azaccount">Connect-AzAccount</a>.</p>
<h2 id="heading-files-in-azure-file-store">Files in Azure File Store</h2>
<p>You will need to upload a backup file to your file <a target="_blank" href="https://azure.microsoft.com/en-us/features/storage-explorer/">Azure File Store</a>. Azure File Store is a quite cheap storage for storing files like database backups. To upload a backup file, I recommend using <a target="_blank" href="https://azure.microsoft.com/en-us/features/storage-explorer/">Azure Storage Explorer</a> with which you can upload your files in a very convenient and secure way.</p>
<h2 id="heading-azure-bastion">Azure Bastion</h2>
<p>Azure Bastion will be set up this script automatically. However, I at this point, want to give you some resources first why and when you should use Azure Bastion. If you’re using a VM via Internet, it is always a bad idea to use a RDP connection without an encrypted tunnel.</p>
<p>https://docs.microsoft.com/en-us/azure/bastion/bastion-overview
https://jussiroine.com/2019/09/building-a-secure-remote-access-solution-using-azure-bastion-host/</p>
<h2 id="heading-dbatools">dbatools</h2>
<p>We are going to install dbatools on the VM after its deployment. At this point, you don’t need to know much about this awesome collection of PowerShell commandlets. It contains 500+ easy and efficient to use scripts that are free and open source.</p>
<h2 id="heading-chocolatey">Chocolatey</h2>
<p>We will also install Chocolatey on the VM. Again, at this point, you don’t really need to know much about it as we will just install some software with Chocolatey. It enables you to automatize, download and install many software with few lines of code, making it very quickly for anybody who has to set up a VM (or computers) from scratch. There is also a paid service by Chocolatey, but we will be using the free service offer.</p>
<h1 id="heading-lets-start-with-the-script">Lets start with the script</h1>
<p>As an overview the following script does the following tasks:</p>
<ul>
<li>Creates the VM with SQL Server</li>
<li>Creates Azure Bastion</li>
</ul>
<p>Following you’ll see the entire script and after this script in the second part of this article, I’ll walk you through it.</p>
<pre><code class="lang-powershell"><span class="hljs-comment"># Variables</span>
<span class="hljs-comment">## Global Settings</span>
<span class="hljs-variable">$Location</span> = <span class="hljs-string">"switzerlandnorth"</span>
<span class="hljs-variable">$ResourceGroupName</span> = <span class="hljs-string">"bloglab"</span>

<span class="hljs-comment">## Settings for Storage</span>
<span class="hljs-variable">$StorageName</span> = <span class="hljs-variable">$ResourceGroupName</span> + <span class="hljs-string">"storage"</span>
<span class="hljs-variable">$StorageSku</span> = <span class="hljs-string">"Standard_LRS"</span>

<span class="hljs-comment">## Settings for Network</span>
<span class="hljs-variable">$InterfaceName</span> = <span class="hljs-variable">$ResourceGroupName</span> + <span class="hljs-string">"ServerInterface"</span>
<span class="hljs-variable">$NsgName</span> = <span class="hljs-variable">$ResourceGroupName</span> + <span class="hljs-string">"nsg"</span>
<span class="hljs-variable">$VNetName</span> = <span class="hljs-variable">$ResourceGroupName</span> + <span class="hljs-string">"VNet"</span>
<span class="hljs-variable">$SubnetName</span> = <span class="hljs-string">"Default"</span>
<span class="hljs-variable">$VNetAddressPrefix</span> = <span class="hljs-string">"10.0.0.0/16"</span>
<span class="hljs-variable">$VNetSubnetAddressPrefix</span> = <span class="hljs-string">"10.0.0.0/24"</span>
<span class="hljs-variable">$TCPIPAllocationMethod</span> = <span class="hljs-string">"Dynamic"</span>
<span class="hljs-variable">$DomainName</span> = <span class="hljs-variable">$ResourceGroupName</span>

<span class="hljs-comment">## Settings for Bastion</span>
<span class="hljs-variable">$publicIpName</span> = <span class="hljs-string">"pip"</span> + <span class="hljs-variable">$ResourceGroupName</span>
<span class="hljs-variable">$BastionName</span> = <span class="hljs-string">"Bastion"</span> + <span class="hljs-variable">$ResourceGroupName</span>
<span class="hljs-variable">$BastionSubnetName</span> = <span class="hljs-string">"AzureBastionSubnet"</span>

<span class="hljs-comment">## Settings for hardware</span>
<span class="hljs-variable">$VMName</span> = <span class="hljs-variable">$ResourceGroupName</span> + <span class="hljs-string">"VM"</span>
<span class="hljs-variable">$ComputerName</span> = <span class="hljs-variable">$ResourceGroupName</span> + <span class="hljs-string">"Server"</span>
<span class="hljs-variable">$VMSize</span> = <span class="hljs-string">"Standard_D4s_v3"</span>
<span class="hljs-variable">$OSDiskName</span> = <span class="hljs-variable">$VMName</span> + <span class="hljs-string">"OSDisk"</span>

<span class="hljs-comment">## Settings for SQL Server licence</span>
<span class="hljs-variable">$PublisherName</span> = <span class="hljs-string">"MicrosoftSQLServer"</span>
<span class="hljs-variable">$OfferName</span> = <span class="hljs-string">"sql2019-ws2019"</span>
<span class="hljs-variable">$Sku</span> = <span class="hljs-string">"SQLDEV"</span>
<span class="hljs-variable">$Version</span> = <span class="hljs-string">"latest"</span>

<span class="hljs-comment"># Set credentials for logging into VM</span>
<span class="hljs-variable">$Credential</span> = <span class="hljs-built_in">Get-Credential</span> <span class="hljs-literal">-Message</span> <span class="hljs-string">"Type the name and password of the local administrator account."</span> 

<span class="hljs-comment"># Creation of Objects</span>
<span class="hljs-comment">## Resource Group</span>
<span class="hljs-built_in">New-AzResourceGroup</span> <span class="hljs-literal">-Name</span> <span class="hljs-variable">$ResourceGroupName</span> <span class="hljs-literal">-Location</span> <span class="hljs-variable">$Location</span>

<span class="hljs-comment">## Storage</span>
<span class="hljs-variable">$StorageAccount</span> = <span class="hljs-built_in">New-AzStorageAccount</span> <span class="hljs-literal">-ResourceGroupName</span> <span class="hljs-variable">$ResourceGroupName</span> <span class="hljs-literal">-Name</span> <span class="hljs-variable">$StorageName</span> <span class="hljs-literal">-SkuName</span> <span class="hljs-variable">$StorageSku</span> <span class="hljs-literal">-Kind</span> <span class="hljs-string">"Storage"</span> <span class="hljs-literal">-Location</span> <span class="hljs-variable">$Location</span>

<span class="hljs-comment">## Network</span>
<span class="hljs-variable">$SubnetConfig</span> = <span class="hljs-built_in">New-AzVirtualNetworkSubnetConfig</span> <span class="hljs-literal">-Name</span> <span class="hljs-variable">$SubnetName</span> <span class="hljs-literal">-AddressPrefix</span> <span class="hljs-variable">$VNetSubnetAddressPrefix</span>
<span class="hljs-variable">$VNet</span> = <span class="hljs-built_in">New-AzVirtualNetwork</span> <span class="hljs-literal">-Name</span> <span class="hljs-variable">$VNetName</span> <span class="hljs-literal">-ResourceGroupName</span> <span class="hljs-variable">$ResourceGroupName</span> <span class="hljs-literal">-Location</span> <span class="hljs-variable">$Location</span> <span class="hljs-literal">-AddressPrefix</span> <span class="hljs-variable">$VNetAddressPrefix</span> <span class="hljs-literal">-Subnet</span> <span class="hljs-variable">$SubnetConfig</span>
<span class="hljs-variable">$PublicIp</span> = <span class="hljs-built_in">New-AzPublicIpAddress</span> <span class="hljs-literal">-Name</span> <span class="hljs-variable">$InterfaceName</span> <span class="hljs-literal">-ResourceGroupName</span> <span class="hljs-variable">$ResourceGroupName</span> <span class="hljs-literal">-Location</span> <span class="hljs-variable">$Location</span> <span class="hljs-literal">-AllocationMethod</span> <span class="hljs-variable">$TCPIPAllocationMethod</span> <span class="hljs-literal">-DomainNameLabel</span> <span class="hljs-variable">$DomainName</span>
<span class="hljs-variable">$NsgRuleRDP</span> = <span class="hljs-built_in">New-AzNetworkSecurityRuleConfig</span> <span class="hljs-literal">-Name</span> <span class="hljs-string">"RDPRule"</span> <span class="hljs-literal">-Protocol</span> Tcp <span class="hljs-literal">-Direction</span> Inbound <span class="hljs-literal">-Priority</span> <span class="hljs-number">1000</span> <span class="hljs-literal">-SourceAddressPrefix</span> * <span class="hljs-literal">-SourcePortRange</span> * <span class="hljs-literal">-DestinationAddressPrefix</span> * <span class="hljs-literal">-DestinationPortRange</span> <span class="hljs-number">3389</span> <span class="hljs-literal">-Access</span> Allow
<span class="hljs-variable">$NsgRuleSQL</span> = <span class="hljs-built_in">New-AzNetworkSecurityRuleConfig</span> <span class="hljs-literal">-Name</span> <span class="hljs-string">"MSSQLRule"</span>  <span class="hljs-literal">-Protocol</span> Tcp <span class="hljs-literal">-Direction</span> Inbound <span class="hljs-literal">-Priority</span> <span class="hljs-number">1001</span> <span class="hljs-literal">-SourceAddressPrefix</span> * <span class="hljs-literal">-SourcePortRange</span> * <span class="hljs-literal">-DestinationAddressPrefix</span> * <span class="hljs-literal">-DestinationPortRange</span> <span class="hljs-number">1433</span> <span class="hljs-literal">-Access</span> Allow
<span class="hljs-variable">$Nsg</span> = <span class="hljs-built_in">New-AzNetworkSecurityGroup</span> <span class="hljs-literal">-ResourceGroupName</span> <span class="hljs-variable">$ResourceGroupName</span> <span class="hljs-literal">-Location</span> <span class="hljs-variable">$Location</span> <span class="hljs-literal">-Name</span> <span class="hljs-variable">$NsgName</span> <span class="hljs-literal">-SecurityRules</span> <span class="hljs-variable">$NsgRuleRDP</span>, <span class="hljs-variable">$NsgRuleSQL</span>
<span class="hljs-variable">$Interface</span> = <span class="hljs-built_in">New-AzNetworkInterface</span> <span class="hljs-literal">-Name</span> <span class="hljs-variable">$InterfaceName</span> <span class="hljs-literal">-ResourceGroupName</span> <span class="hljs-variable">$ResourceGroupName</span> <span class="hljs-literal">-Location</span> <span class="hljs-variable">$Location</span> <span class="hljs-literal">-SubnetId</span> <span class="hljs-variable">$VNet</span>.Subnets[<span class="hljs-number">0</span>].Id <span class="hljs-literal">-PublicIpAddressId</span> <span class="hljs-variable">$PublicIp</span>.Id <span class="hljs-literal">-NetworkSecurityGroupId</span> <span class="hljs-variable">$Nsg</span>.Id

<span class="hljs-comment">## Compute</span>
<span class="hljs-variable">$VirtualMachine</span> = <span class="hljs-built_in">New-AzVMConfig</span> <span class="hljs-literal">-VMName</span> <span class="hljs-variable">$VMName</span> <span class="hljs-literal">-VMSize</span> <span class="hljs-variable">$VMSize</span>
<span class="hljs-variable">$VirtualMachine</span> = <span class="hljs-built_in">Set-AzVMOperatingSystem</span> <span class="hljs-literal">-VM</span> <span class="hljs-variable">$VirtualMachine</span> <span class="hljs-literal">-Windows</span> <span class="hljs-literal">-ComputerName</span> <span class="hljs-variable">$ComputerName</span> <span class="hljs-literal">-Credential</span> <span class="hljs-variable">$Credential</span> <span class="hljs-literal">-ProvisionVMAgent</span> <span class="hljs-literal">-EnableAutoUpdate</span> <span class="hljs-comment">#-TimeZone = $TimeZone</span>
<span class="hljs-variable">$VirtualMachine</span> = <span class="hljs-built_in">Add-AzVMNetworkInterface</span> <span class="hljs-literal">-VM</span> <span class="hljs-variable">$VirtualMachine</span> <span class="hljs-literal">-Id</span> <span class="hljs-variable">$Interface</span>.Id
<span class="hljs-variable">$OSDiskUri</span> = <span class="hljs-variable">$StorageAccount</span>.PrimaryEndpoints.Blob.ToString() + <span class="hljs-string">"vhds/"</span> + <span class="hljs-variable">$OSDiskName</span> + <span class="hljs-string">".vhd"</span>
<span class="hljs-variable">$VirtualMachine</span> = <span class="hljs-built_in">Set-AzVMOSDisk</span> <span class="hljs-literal">-VM</span> <span class="hljs-variable">$VirtualMachine</span> <span class="hljs-literal">-Name</span> <span class="hljs-variable">$OSDiskName</span> <span class="hljs-literal">-VhdUri</span> <span class="hljs-variable">$OSDiskUri</span> <span class="hljs-literal">-Caching</span> ReadOnly <span class="hljs-literal">-CreateOption</span> FromImage

<span class="hljs-comment">## Image</span>
<span class="hljs-variable">$VirtualMachine</span> = <span class="hljs-built_in">Set-AzVMSourceImage</span> <span class="hljs-literal">-VM</span> <span class="hljs-variable">$VirtualMachine</span> <span class="hljs-literal">-PublisherName</span> <span class="hljs-variable">$PublisherName</span> <span class="hljs-literal">-Offer</span> <span class="hljs-variable">$OfferName</span> <span class="hljs-literal">-Skus</span> <span class="hljs-variable">$Sku</span> <span class="hljs-literal">-Version</span> <span class="hljs-variable">$Version</span>

<span class="hljs-comment">## Create the VM in Azure</span>
<span class="hljs-built_in">New-AzVM</span> <span class="hljs-literal">-ResourceGroupName</span> <span class="hljs-variable">$ResourceGroupName</span> <span class="hljs-literal">-Location</span> <span class="hljs-variable">$Location</span> <span class="hljs-literal">-VM</span> <span class="hljs-variable">$VirtualMachine</span>

<span class="hljs-comment">## Add the SQL IaaS Extension with chosen license type</span>
<span class="hljs-built_in">New-AzSqlVM</span> <span class="hljs-literal">-ResourceGroupName</span> <span class="hljs-variable">$ResourceGroupName</span> <span class="hljs-literal">-Name</span> <span class="hljs-variable">$VMName</span> <span class="hljs-literal">-Location</span> <span class="hljs-variable">$Location</span> <span class="hljs-literal">-LicenseType</span> PAYG

<span class="hljs-comment">## Create Azure Bastion Host</span>
<span class="hljs-built_in">Add-AzVirtualNetworkSubnetConfig</span> <span class="hljs-literal">-VirtualNetwork</span> <span class="hljs-variable">$VNet</span> <span class="hljs-literal">-Name</span> <span class="hljs-variable">$BastionSubnetName</span> <span class="hljs-literal">-AddressPrefix</span> <span class="hljs-number">10.0</span>.<span class="hljs-number">1.0</span>/<span class="hljs-number">27</span>
<span class="hljs-variable">$VNet</span> | <span class="hljs-built_in">Set-AzVirtualNetwork</span>
<span class="hljs-variable">$publicip</span> = <span class="hljs-built_in">New-AzPublicIpAddress</span> <span class="hljs-literal">-ResourceGroupName</span> <span class="hljs-variable">$ResourceGroupName</span> <span class="hljs-literal">-name</span> <span class="hljs-variable">$publicIpName</span> <span class="hljs-literal">-location</span> <span class="hljs-variable">$Location</span> <span class="hljs-literal">-AllocationMethod</span> <span class="hljs-keyword">Static</span> <span class="hljs-literal">-Sku</span> Standard
<span class="hljs-built_in">New-AzBastion</span> <span class="hljs-literal">-ResourceGroupName</span> <span class="hljs-variable">$ResourceGroupName</span> <span class="hljs-literal">-Name</span> <span class="hljs-variable">$BastionName</span> <span class="hljs-literal">-PublicIpAddress</span> <span class="hljs-variable">$publicip</span> <span class="hljs-literal">-VirtualNetworkId</span> <span class="hljs-variable">$VNet</span>.id
</code></pre>
<p>I will walk you through some major settings that the code above has so you may alter them accordingly to your needs.</p>
<pre><code class="lang-PowerShell"><span class="hljs-variable">$Location</span> = <span class="hljs-string">"switzerlandnorth"</span>
<span class="hljs-variable">$ResourceGroupName</span> = <span class="hljs-string">"bloglab"</span>
</code></pre>
<p>Since I live and work in Greater Zurich Area, I’ve picked switzerlandnorth as my hosted location for Azure. If you want to pick a different one but don’t know your choices, you can get a list with Get-AzLocation.</p>
<pre><code class="lang-powershell"><span class="hljs-variable">$ResourceGroupName</span>
</code></pre>
<p>Defines the name of your Resource Group. You can think of it as a container that holds all objects you've got within it. This makes configurations for your objects easier. For example, in order to copy or move your objects from "switzerlandnorth" (Zurich) to "switzerlandwest" (Geneva) would mean that you only need to address the container and every single object within this group.</p>
<pre><code class="lang-PowerShell"><span class="hljs-variable">$StorageSku</span> = <span class="hljs-string">"Standard_LRS"</span>
</code></pre>
<p>This defines what kind of storage you will use. For a better decision what choices you have, check its documentation. In our example, I’ve picked the standard type. Please note that there may be pricing variations amongst the different types which you may want to find out about here.</p>
<pre><code class="lang-PowerShell"><span class="hljs-comment"># Set credentials for logging into VM</span>
<span class="hljs-variable">$Credential</span> = <span class="hljs-built_in">Get-Credential</span> <span class="hljs-literal">-Message</span> <span class="hljs-string">"Type the name and password of the local administrator account."</span>
</code></pre>
<p>Shortly after you start the script, you’ll notice that you need to enter some credentials. This is actually this line that cast this question. In order to access the VM, you need to pass exactly this credentials. In other words, during the creation of the VM, your initial credentials are set so you may access it.</p>
<pre><code class="lang-PowerShell"><span class="hljs-comment">## Settings for Network</span>
</code></pre>
<p>Next, you’ll see some networking configurations. These configurations have to be made because you’ll have a virtual net around your virtual machine. You need it also for Azure Bastion which basically lets you access your VM via a secure tunnel via SSL. Your browser will provide the actual access. I’ll show you more about this later, but for now, it is sufficient to say that Azure Bastion makes it easy for you to tunnel your RDP connection. It is not exactly free but it allows you to create a secure RDP connection if you don’t have the means to create an own VPN tunnel. You can find out more about the pricing for Azure Bastion here.</p>
<pre><code class="lang-PowerShell"><span class="hljs-comment">## Settings for Bastion</span>
<span class="hljs-variable">$BastionAddressPrefix</span> = <span class="hljs-string">"10.0.1.0/27"</span>
</code></pre>
<p>In the settings for Azure Bastion, we define some names. I’ve used the pretty standard naming conventions that Microsoft suggests here. The last line in these settings is what I want to emphasize here. It is important to note that $BastionAddressPrefix needs to be at least /27 as Microsoft states in its documentation in the bullet point “Subnet” here. This can be a source of confusion if you don’t pay attention to this.</p>
<pre><code class="lang-PowerShell"><span class="hljs-comment">## Settings for hardware</span>
With <span class="hljs-variable">$VMSize</span> = <span class="hljs-string">"Standard_D4s_v3"</span>
<span class="hljs-variable">$OSDiskName</span> = <span class="hljs-variable">$VMName</span> + <span class="hljs-string">"OSDisk"</span>
</code></pre>
<p>With this we define some VM specific “hardware”. Again, with what you decide to go for, the pricing may change. To find out more about this, Microsoft provides a calculator for pricing where you may get your estimates. Some more information about the right sizing for your VM provides Thomas Maurer, a Senior Cloud Advocate at Microsoft. If you want to list which options you have for your preferred location, you can use</p>
<pre><code class="lang-PowerShell"><span class="hljs-built_in">Get-AzVMSize</span> <span class="hljs-literal">-Location</span> <span class="hljs-string">"switzerlandnorth"</span>
</code></pre>
<p>SQL Server License
Since we actually want to have a SQL Server on the VM, we also need to decide what kind of licensing we want. In this installation, we are going to use. If you want to find out what options you have for your preferred location you can type:</p>
<pre><code class="lang-PowerShell"><span class="hljs-comment">## Settings for SQL Server licence</span>
<span class="hljs-built_in">Get-AzVMImageOffer</span> <span class="hljs-literal">-Location</span> switzerlandnorth <span class="hljs-literal">-Publisher</span> <span class="hljs-string">'MicrosoftSQLServer'</span>
</code></pre>
<p>For more information about the costs and licensing can be found on Microsofts documentation about <a target="_blank" href="https://docs.microsoft.com/en-us/azure/azure-sql/virtual-machines/windows/pricing-guidance?view=azuresql">Pricing guidance for SQL Server on Azure VMs</a>.</p>
<p>So with the code we have so far, we now configured almost all variables. The rest of the code actually creates the needed objects.</p>
<p>If you run the code, you’ll notice that it’ll take about 5 minutes until it continues at some point. The creation of Azure Bastion host takes some while. After the code has completed, after about 20 minutes, you’ll have a fair amount of objects in your resource group, being the VM and the Bastion host amongst them.</p>
<p>I will show you how to set up the generated VM with a SQL Database and various software in an automated way.</p>
<h1 id="heading-starting-and-setting-up-the-vm">Starting and setting up the VM</h1>
<p>Now the VM is deployed and already started. Now is also the moment you’ve been waiting for – you want to access to your VM. In order to do so, go to your Azure portal and find your resource and your VM in there. Once you’re clicked on your VM, you’ll see a screen like below. Here, click on “Connect” as below and choose the option Bastion.
<img src="Bild1.png" alt /></p>
<p>After this, you’ll see a site with a button “Use Bastion” on which you click. In the next window, you need to enter the credentials you’ve set for this VM during the running of the script. Your browser starts to connect to your VM. During that time, you will see a small window asking if you want to allow copy &amp; paste. If you allow it, you can copy &amp; paste text onto the VM. It is not possible to copy files into the VM on this way. However, like I’ve already mentioned in the prerequisites, you can use the Azure File Storage Explorer with which you can use drag and drop to upload files to this File Storage. You can map this storage as a network drive so you can access even larger files very conveniently that way. The next few steps will allow you do so.</p>
<p>After your VM started and you see Windows, you might want to have a full screen view on your VM. To do so, click on this &gt;&gt; on the left side</p>
<p><img src="Bild2.png" alt /></p>
<p>This will slide open a small window like this. In here, you can choose full screen:</p>
<p><img src="Bild3.png" alt /></p>
<p>And add a File Share:</p>
<pre><code class="lang-PowerShell"><span class="hljs-comment"># Enabling execution of PowerShell scripts:</span>
<span class="hljs-built_in">set-executionpolicy</span> remotesigned <span class="hljs-literal">-Force</span>

<span class="hljs-comment"># Mounting Azure File Store as cheap data storage </span>
<span class="hljs-variable">$connectTestResult</span> = <span class="hljs-built_in">Test-NetConnection</span> <span class="hljs-literal">-ComputerName</span> <span class="hljs-variable">$YourDataStorageName</span>.file.core.windows.net <span class="hljs-literal">-Port</span> <span class="hljs-number">445</span>
<span class="hljs-keyword">if</span> (<span class="hljs-variable">$connectTestResult</span>.TcpTestSucceeded) {
    <span class="hljs-comment"># Save the password so the drive will persist on reboot</span>
    cmd.exe /C <span class="hljs-string">"cmdkey /add:`"<span class="hljs-variable">$YourDataStorageName</span>.file.core.windows.net`" /user:`"Azure\sqlsrvbaks`" /pass:`"<span class="hljs-variable">$YourPassword</span>`""</span>
    <span class="hljs-comment"># Mount the drive</span>
    <span class="hljs-built_in">New-PSDrive</span> <span class="hljs-literal">-Name</span> Z <span class="hljs-literal">-PSProvider</span> FileSystem <span class="hljs-literal">-Root</span> <span class="hljs-string">"\\YourDataStorageName.file.core.windows.net\wideworldimporters"</span> <span class="hljs-literal">-Persist</span>
}
<span class="hljs-keyword">else</span> {
    <span class="hljs-built_in">Write-Error</span> <span class="hljs-literal">-Message</span> <span class="hljs-string">"Unable to reach the Azure storage account via port 445. Check to make sure your organization or ISP is not blocking port 445, or use Azure P2S VPN, Azure S2S VPN, or Express Route to tunnel SMB traffic over a different port."</span>
}
</code></pre>
<p>With this, we are now able to install chocolatey:</p>
<pre><code class="lang-PowerShell"><span class="hljs-comment"># Install Chocolatey:</span>
<span class="hljs-built_in">Set-ExecutionPolicy</span> Bypass <span class="hljs-literal">-Scope</span> <span class="hljs-keyword">Process</span> <span class="hljs-literal">-Force</span>; [<span class="hljs-type">System.Net.ServicePointManager</span>]::SecurityProtocol = [<span class="hljs-type">System.Net.ServicePointManager</span>]::SecurityProtocol <span class="hljs-operator">-bor</span> <span class="hljs-number">3072</span>; <span class="hljs-built_in">iex</span> ((<span class="hljs-built_in">New-Object</span> System.Net.WebClient).DownloadString(<span class="hljs-string">'https://chocolatey.org/install.ps1'</span>))
Chocolatey makes it extremely easy to install a lot of software with just one PowerShell line as following:

<span class="hljs-comment"># Install Chocolatey GUI and other software:</span>
choco install chocolateygui microsoft<span class="hljs-literal">-edge</span> tabular<span class="hljs-literal">-editor</span> daxstudio azure<span class="hljs-literal">-data</span><span class="hljs-literal">-studio</span> git dbatools vscode <span class="hljs-literal">-y</span>
</code></pre>
<p>This installs:</p>
<ul>
<li><a target="_blank" href="https://community.chocolatey.org/packages/ChocolateyGUI">Chocolatey GUI</a></li>
<li><a target="_blank" href="https://community.chocolatey.org/packages/microsoft-edge">Microsoft Edge</a></li>
<li><a target="_blank" href="https://community.chocolatey.org/packages/tabular-editor">Tabular Editor</a></li>
<li><a target="_blank" href="https://community.chocolatey.org/packages/daxstudio">Dax Studio</a></li>
<li><a target="_blank" href="https://community.chocolatey.org/packages/git.install">Git</a></li>
<li><a target="_blank" href="https://community.chocolatey.org/packages/dbatools">Dbatools</a></li>
<li><a target="_blank" href="https://community.chocolatey.org/packages/vscode">Visual Studio Code</a></li>
</ul>
<p>Of course, you could add / remove the software as you like in that line. The last -y means that you agree to the installs of all listed software. dbatools takes quite some time to be installed (ca. 10 minutes) so don’t be surprised if it seems to be stuck – it is not.</p>
<p>With this Tabular Editor, .Net Framework 4.8 gets installed. Therefore we need to reboot the VM:</p>
<pre><code class="lang-PowerShell"><span class="hljs-comment"># Rebooting Computer to complete installs</span>
<span class="hljs-built_in">Restart-Computer</span>
</code></pre>
<p>You just can keep the browser tab for the VM open until the reboot is done and you can continue.</p>
<p>With dbatools being installed, we would like to restore our databases. But, remember – we need to have the backup files first which we need to bring to our VM first? Now it is the time to map Azure File Store to our VM. If you go to Azure Portal, go to</p>
<p><img src="Bild4.png" alt /></p>
<p>And add a File Share</p>
<p><img src="Bild5.png" alt /></p>
<p>And click on the created file share next. In the next window, click on Connect which will open a side window. In here, you can define a mapping letter (eg. Z) and it shows a some code with which you can map your file store.</p>
<p><img src="Bild6.png" alt /></p>
<p>Copy this code (attention: this has a password in it, keep this secret). Paste this into PowerShell ISE or put it into a file and run it on your VM. As a result, you should see something like this:</p>
<p><img src="Bild7.png" alt /></p>
<p>If you’ve uploaded a file via Microsoft Storage Explorer, you’ll see the file here. In order to restore this file or even more files, you can use the following code:</p>
<pre><code class="lang-PowerShell"><span class="hljs-comment"># Restore Parameters</span>
<span class="hljs-variable">$SQLBackupSource</span> = <span class="hljs-string">"Z:\*.bak"</span>
<span class="hljs-comment"># Restore databases</span>
<span class="hljs-built_in">Copy-Item</span> <span class="hljs-variable">$SQLBackupSource</span> <span class="hljs-literal">-Destination</span> <span class="hljs-string">"C:\Program Files\Microsoft SQL Server\MSSQL15.MSSQLSERVER\MSSQL\Backup"</span> <span class="hljs-literal">-Force</span> <span class="hljs-literal">-Verbose</span>
<span class="hljs-built_in">Restore-DbaDatabase</span> <span class="hljs-literal">-SqlInstance</span> localhost <span class="hljs-literal">-Path</span> <span class="hljs-string">"C:\Program Files\Microsoft SQL Server\MSSQL15.MSSQLSERVER\MSSQL\Backup\"</span>
</code></pre>
<p>In order to shut down your VM via Code or removing it or even deleting the entire resource group with all its objects in it, those few lines may help you out:</p>
<pre><code class="lang-PowerShell"><span class="hljs-comment"># For stopping this VM: </span>
<span class="hljs-built_in">Stop-AzVM</span> <span class="hljs-literal">-Name</span> <span class="hljs-variable">$VMName</span> <span class="hljs-literal">-ResourceGroupName</span> <span class="hljs-variable">$ResourceGroupName</span>

<span class="hljs-comment"># # For removing this VM: </span>
<span class="hljs-built_in">Remove-AzVM</span> <span class="hljs-literal">-ResourceGroupName</span> <span class="hljs-variable">$ResourceGroupName</span> <span class="hljs-literal">-Name</span> <span class="hljs-variable">$VMName</span>

<span class="hljs-comment"># For removing resource group (and all its objects, including this VM):</span>
<span class="hljs-variable">$ResourceGroupName</span> = <span class="hljs-string">"bloglab"</span>
<span class="hljs-built_in">Remove-AzResourceGroup</span> <span class="hljs-literal">-Name</span> <span class="hljs-variable">$ResourceGroupName</span> <span class="hljs-literal">-Force</span>
</code></pre>
<p>This was just a small dive into this topic. Of course it would be possible to automatize this even more in a wholesome DevOps way. It also might be interesting to know that Microsoft offers some tools that may support this idea with:</p>
<ul>
<li><a target="_blank" href="https://docs.microsoft.com/en-us/azure/automation/">Azure Automation</a></li>
<li><a target="_blank" href="https://docs.microsoft.com/en-us/azure/lab-services/tutorial-setup-lab-account">Azure Lab</a></li>
<li><a target="_blank" href="https://azure.microsoft.com/de-de/services/devops/">Azure DevOps</a></li>
</ul>
<p>If you have a question, feel free to contact me via <a target="_blank" href="https://www.linkedin.com/in/kaysauter/">LinkedIn</a>!</p>
]]></content:encoded></item></channel></rss>