<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom"><title>Edi Wang</title><subtitle>Microsoft MVP for Azure</subtitle><rights>© 2009 - 2026 edi.wang</rights><updated>2026-11-11T12:42:52Z</updated><generator uri="https://edi.wang">Moonglade v16.10.0-beta.1</generator><entry><id>f7fc8757-ce8e-4bb3-8c8d-4f11848fc9b4</id><title>How to Stop Automatically Switching to New Outlook</title><updated>2025-01-20T02:10:14Z</updated><published>2025-01-20T02:10:14Z</published><link href="https://edi.wang/post/2025/1/20/how-to-stop-automatically-switching-to-new-outlook" hreflang="en-us" /><author><name>Edi Wang</name><email>Edi.Wang@outlook.com</email></author><category term="Server and Tools" /><content>&lt;h2&gt;Problem&lt;/h2&gt;
&lt;hr&gt;
&lt;p dir="auto"&gt;Microsoft introduced the new Outlook last year, a web-based application designed to replace both the built-in Outlook in Windows 11 and the classic Outlook in Microsoft 365. However, many users, including myself, are not fond of the new Outlook.&lt;/p&gt;
&lt;p dir="auto"&gt;In early 2025, I discovered that Microsoft plans to automatically install the new Outlook on new Windows 11 machines. Additionally, users will be forced to switch to the new Outlook after opening the classic Outlook app a few times. What&amp;rsquo;s even more frustrating is that even if you uninstall the new Outlook app, it will reappear after some time. Microsoft is essentially forcing users to adopt the new Outlook, regardless of their preferences, and this has been a source of frustration for me.&lt;/p&gt;
&lt;p dir="auto"&gt;In this blog post, I&amp;rsquo;ll share a method to disable this automatic upgrade behavior. Hopefully, this solution will allow you to avoid the new Outlook for an extended period without being repeatedly inconvenienced.&lt;/p&gt;
&lt;h2&gt;Solution&lt;/h2&gt;
&lt;hr&gt;
&lt;h3&gt;1. Uninstall the new Outlook App&lt;/h3&gt;
&lt;p&gt;Close all Outlook instances, and go to Settings - Apps - Installed apps, and uninstall the new Outlook if it's present.&lt;/p&gt;
&lt;h3&gt;2. Edit Registry&lt;/h3&gt;
&lt;p&gt;Press &lt;code&gt;Windows Key + R&lt;/code&gt; and run &lt;code&gt;regedit&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Go to &lt;code&gt;HKEY_CURRENT_USER\Software\Microsoft\Office\16.0\Outlook\Options\General&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Add a &lt;code&gt;32-bit DWORD&lt;/code&gt; with name &lt;code&gt;HideNewOutlookToggle &lt;/code&gt;and value of &lt;code&gt;1&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Delete &lt;code&gt;NewOutlookAutoMigrationStage&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Delete&amp;nbsp;&lt;code&gt;NewOutlookAutoMigrationType&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="/image/img-10b6057c-33ec-4d03-86d2-6e33ddee9a91.png" border="0" alt="" loading="lazy"&gt;&lt;/p&gt;
&lt;p&gt;Delete the entire &lt;code&gt;\HKEY_CURRENT_USER\Software\Microsoft\Office\16.0\Outlook\NewOutlook&lt;/code&gt; key&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-e643fbbd-b805-4d90-ab32-0fdc88891b4c.png" border="0" alt="" loading="lazy"&gt;&lt;/p&gt;
&lt;p&gt;Go to HKEY_CURRENT_USER\Software\Microsoft\Office\16.0\Outlook\Preferences&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Add a &lt;code&gt;32-bit DWORD&lt;/code&gt; with name &lt;code&gt;UseNewOutlook &lt;/code&gt;and value of &lt;code&gt;0&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Delete &lt;code&gt;NewOutlookRenudgeStartDate&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Delete &lt;code&gt;NewOutlookRenudgeWatermark&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="/image/img-47da6f81-b7d2-4c58-bc39-baddd91ea781.png" border="0" alt="" loading="lazy"&gt;&lt;/p&gt;
&lt;h3&gt;3. Restart computer and classic Outlook&lt;/h3&gt;
&lt;p&gt;Finally, restart your computer and then open the classic outlook that you liked for 30 years.&lt;/p&gt;
&lt;p&gt;Now, the "Use new Outlook" switch is completely gone, and you won't be forced to use the new Outlook next time you start the classic Outlook!&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-f151daa9-5d98-4281-ac58-15599230db6f.png" border="0" loading="lazy"&gt;&lt;/p&gt;</content></entry><entry><id>6e9c4f1b-1922-459b-bac6-99424502ce59</id><title>How to Prevent Untrusted Apps from Reading Your Browser Password on Windows</title><updated>2024-05-13T07:58:06Z</updated><published>2024-05-13T07:58:06Z</published><link href="https://edi.wang/post/2024/5/13/how-to-prevent-untrusted-apps-from-reading-your-browser-password-on-windows" hreflang="en-us" /><author><name>Edi Wang</name><email>Edi.Wang@outlook.com</email></author><category term="Server and Tools" /><content>&lt;h2&gt;Problem&lt;/h2&gt;
&lt;hr&gt;
&lt;p dir="auto"&gt;Although Microsoft Edge, Chrome, and Firefox store encrypted passwords in the user profile location, these can be easily decrypted by another program without requiring user authentication or approval. Notably, Windows does not trigger a User Account Control (UAC) dialog, nor does it request a PIN, facial recognition, or two-factor authentication (2FA) before decrypting the passwords. Consequently, an untrusted program, not flagged by Microsoft Defender or other security software, can easily access your passwords.&lt;/p&gt;
&lt;p dir="auto"&gt;The screenshot below demonstrates a utility specifically designed to extract all your stored passwords from Microsoft Edge, Chrome, and Firefox.&lt;/p&gt;
&lt;p dir="auto"&gt;In this blog post, I will demonstrate the simplest method to prevent untrusted programs from accessing your browser passwords.&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-c4033769-3013-4ecc-b62e-48c4fbf733b9.png" border="0" loading="lazy"&gt;&lt;/p&gt;
&lt;h2&gt;Solution&lt;/h2&gt;
&lt;hr&gt;
&lt;h3&gt;Create a new user&lt;/h3&gt;
&lt;p&gt;First, we need to create a new user on the system. Start PowerShell as Administrator, and run the following command:&lt;/p&gt;
&lt;pre class="language-powershell"&gt;&lt;code&gt;net user &amp;lt;username&amp;gt; &amp;lt;password&amp;gt; /add&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;replace &lt;code&gt;&amp;lt;username&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;password&amp;gt;&lt;/code&gt; with your own values.&lt;/p&gt;
&lt;p&gt;Please &lt;span style="color: #e03e2d;"&gt;&lt;strong&gt;DO NOT, NEVER, EVER&lt;/strong&gt;&lt;/span&gt;, add this account to Administrators group.&lt;/p&gt;
&lt;h3&gt;Run untrusted App as this user&lt;/h3&gt;
&lt;p&gt;Holding Shift while right click the untrusted program and select "Run as different user".&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-f80ddb2c-045c-4f6a-9c21-fefeebda76fd.png" border="0" loading="lazy"&gt;&lt;/p&gt;
&lt;p&gt;Enter the credentials of the newly created user&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-6930d6e9-f048-4908-818c-5eed28755805.png" border="0" loading="lazy"&gt;&lt;/p&gt;
&lt;p&gt;Now the program won't be able to get any of your browser passwords.&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-e4f83a7d-ff45-4795-a97f-a0a07af3cd6f.png" border="0" loading="lazy"&gt;&lt;/p&gt;
&lt;h3&gt;Make an App always run as different user every time&lt;/h3&gt;
&lt;p&gt;If you use the untrusted app very often, you can make it automatically run as different user every time without typing username and password.&lt;/p&gt;
&lt;p&gt;To achieve this, create or modify the short cut of the exe file, change the "Target" as below, for example, I would like to run WeChat as user old6 every time.&lt;/p&gt;
&lt;pre class="language-powershell"&gt;&lt;code&gt;C:\Windows\System32\runas.exe /savecred /user:old6 "C:\Program Files\Tencent\WeChat\WeChat.exe"&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src="/image/img-a4cb0c41-5094-4e3a-9498-9c131445c171.png" border="0" loading="lazy"&gt;&lt;/p&gt;
&lt;p&gt;In this command, &lt;code&gt;runas.exe /savecred /user:old6&lt;/code&gt; will let user input the password for user old6 only the first time. Then, you can just double click the shortcut to start the App as user old 6 every time automatically.&lt;/p&gt;
&lt;h2&gt;Explain&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;Microsoft Edge, Chrome and Firefox stores encrypted password in current user's profile folder.&amp;nbsp;For example, for Microsoft Edge, the storage location is:&amp;nbsp;&lt;/p&gt;
&lt;pre class="language-plaintext"&gt;&lt;code&gt;C:\Users\&amp;lt;username&amp;gt;\AppData\Local\Microsoft\Edge\User Data\Default\Login Data&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This file is in SQLite database format. You can open it by SQLite browser. You can see the encrypted &lt;strong&gt;password_value &lt;/strong&gt;field for each website where your password is stored.&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-c8a8ffb9-18bf-4af4-9eba-e421769857cc.png" border="0" loading="lazy"&gt;&lt;/p&gt;
&lt;p&gt;To decrypt the password into clear text, a program will call &lt;a href="https://en.wikipedia.org/wiki/Data_Protection_API" target="_blank" rel="noopener"&gt;&lt;strong&gt;Data Protection Application Programming Interface&lt;/strong&gt;&amp;nbsp;(&lt;strong&gt;DPAPI&lt;/strong&gt;)&lt;/a&gt; on Windows. This is by design a silent process that does not require Administrator permission or any user interaction.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;You can find hidden credential files in these two locations:&lt;/p&gt;
&lt;pre class="language-powershell"&gt;&lt;code&gt;Get-ChildItem -Hidden C:\Users\&amp;lt;username&amp;gt;\AppData\Local\Microsoft\Credentials\
Get-ChildItem -Hidden C:\Users\&amp;lt;username&amp;gt;\AppData\Roaming\Microsoft\Credentials\&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then find master keys by SID&lt;/p&gt;
&lt;pre class="language-powershell"&gt;&lt;code&gt;Get-ChildItem C:\Users\&amp;lt;username&amp;gt;\AppData\Roaming\Microsoft\Protect\
Get-ChildItem -Hidden C:\Users\&amp;lt;username&amp;gt;\AppData\Roaming\Microsoft\Protect\{SID}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There are plenty of articles online teach you how to decrypt passwords by DPAPI, I will not go through the details in this post.&lt;/p&gt;
&lt;p&gt;However, there is one security limit by the system, which is the DPAPI could only decrypt logged in user's data, which in most case is just a valid program running as the current user.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;DPAPI uses a user-specific key to encrypt and decrypt data. This key is derived from the user's login credentials. Therefore, data encrypted by one user cannot be decrypted by another user unless they have access to the original user's credentials or the decryption key.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;When you run a program under a different user account, that program operates with the permissions and environment of that user. This includes using the DPAPI keys associated with that user account, not your main account. As a result, the program should not be able to decrypt data encrypted under your account.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;However, &lt;span style="color: #e03e2d;"&gt;&lt;strong&gt;an administrator account can still have method to call DPAPI to decrypt password of another user that is currently logged in on the machine&lt;/strong&gt;&lt;/span&gt;. This is how I come to the idea of letting untrusted program run as different user that is not administrator.&amp;nbsp;&lt;/p&gt;
&lt;h3&gt;Reference&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/deployedge/microsoft-edge-security-password-manager-security?WT.mc_id=AZ-MVP-5002809" target="_blank" rel="noopener"&gt;Microsoft Edge password manager security&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://kylemistele.medium.com/stealing-saved-browser-passwords-your-new-favorite-post-exploitation-technique-c5e72c86159a" target="_blank" rel="noopener"&gt;Stealing Saved Browser Passwords: Your New Favorite Post-Exploitation Technique&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.nirsoft.net/2022/01/03/recover-dpapi-encrypted-passwords-created-under-microsoft-account-from-external-drive/" target="_blank" rel="noopener"&gt;Recover DPAPI-encrypted passwords created under Microsoft account from external drive&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://posts.specterops.io/operational-guidance-for-offensive-user-dpapi-abuse-1fb7fac8b107" target="_blank" rel="noopener"&gt;Operational Guidance for Offensive User DPAPI Abuse&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content></entry><entry><id>d885211d-e7d1-4db2-b475-9dc55b73f134</id><title>How to Run Windows XP and Windows 2000 VM on Azure in 2024</title><updated>2024-04-11T01:57:28Z</updated><published>2024-04-11T01:57:28Z</published><link href="https://edi.wang/post/2024/4/11/how-to-run-windows-xp-and-windows-2000-vm-on-azure-in-2024" hreflang="en-us" /><author><name>Edi Wang</name><email>Edi.Wang@outlook.com</email></author><category term="Server and Tools" /><category term="Microsoft Azure" /><content>&lt;h2&gt;Background&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;Microsoft Azure does not officially provide Windows XP images for the creation of virtual machines running this legacy operating system. As of now, the earliest version of a client OS image available on Azure is Windows 7, which additionally requires a Visual Studio subscription to access. Despite this, I have devised a method to operate legacy Windows operating systems, including Windows XP, on the current Azure compute platform. While there is little practical reason to use Windows XP in today's environment, my goal is to demonstrate the feasibility of this endeavor. In this blog post, I will outline the steps involved in this process in detail. I hope you find the journey as enjoyable as the destination.&lt;/p&gt;
&lt;div class="alert alert-warning"&gt;
&lt;h3&gt;Important Security Warning&lt;/h3&gt;
&lt;p&gt;The purpose of this post is to demonstrate a technical possibility, rather than to endorse or recommend such a setup for actual use. The blog post should not be taken as encouragement or endorsement to run unsupported operating systems in a production environment or for any purpose that could put your data or security at risk.&lt;/p&gt;
&lt;p dir="auto"&gt;&lt;strong&gt;Both Windows XP and Windows 2000 are long been out of support by Microsoft.&lt;/strong&gt;&amp;nbsp;This means they no longer receive security updates or patches to protect against the latest vulnerabilities. Running these outdated operating systems poses a significant security risk to any network or system they are part of, including potential exposure to malware, ransomware, and other security threats that could compromise not only the virtual machine but potentially other connected services and data.&lt;/p&gt;
&lt;p dir="auto"&gt;&lt;strong&gt;Running Windows XP or Windows 2000 on Azure&amp;mdash;or any environment in 2024&amp;mdash;should be considered extremely risky.&lt;/strong&gt; If these systems are exposed to the internet, the risk is even greater, as they could be easily targeted by attackers. Even within a closed network, vulnerabilities could be exploited through other connected systems.&lt;/p&gt;
&lt;/div&gt;
&lt;h2&gt;Create Windows XP VM on Local Machine&lt;/h2&gt;
&lt;hr&gt;
&lt;h3&gt;Hyper-V Setup&lt;/h3&gt;
&lt;p&gt;The underlying infrastructure that enables Azure to run virtual machines is essentially a specialized variant of Hyper-V server technology, which is the same hypervisor used in modern Windows client and server operating systems. This implies that, theoretically, any OS that is capable of running on a Hyper-V host in a local environment should also be operable on Azure's platform. This forms the basis for our endeavor to set up Windows XP on Hyper-V first, before transitioning it to Azure.&lt;/p&gt;
&lt;p&gt;The image I use is &lt;strong&gt;Windows XP Professional with SP3 (English, United States) 32bit&lt;/strong&gt;, you can find it on VS subscription or anywhere on internet.&lt;/p&gt;
&lt;p&gt;Please note, we can only use &lt;strong&gt;Professional &lt;/strong&gt;edition as it has &lt;strong&gt;Remote Desktop&lt;/strong&gt; capability to be remotely connected, otherwise you will have no way to use the VM once it's in Azure.&lt;/p&gt;
&lt;p&gt;My host machine is &lt;strong&gt;Windows 11 Pro&lt;/strong&gt; with Hyper-V installed.&lt;/p&gt;
&lt;p&gt;There are a few important steps I listed below.&lt;/p&gt;
&lt;h4&gt;Create a fix size VHD disk&lt;/h4&gt;
&lt;p&gt;Before going to OS setup, you need to create a VHD format hard disk, because Azure doesn't accept VHDX.&amp;nbsp; &lt;strong&gt;4GB&lt;/strong&gt; will be fine.&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-2956e5ef-25f0-480e-a5cb-7d3d8144ab9a.png" border="0" loading="lazy"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-fd068689-0c0d-4892-a5ab-4798736d8549.png" border="0" loading="lazy"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-d7fa3ab4-b861-4183-9b82-dd9ee972b1d5.png" border="0" loading="lazy"&gt;&lt;/p&gt;
&lt;h4&gt;Use Generation 1 VM&lt;/h4&gt;
&lt;p&gt;You must also use Generation 1 when creating this VM.&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-3c69536e-91bb-4924-9f79-2652910fa3e9.png" border="0" loading="lazy"&gt;&lt;/p&gt;
&lt;h4&gt;Use the fix sized VHD&lt;/h4&gt;
&lt;p&gt;Remember to choose the fixed sized VHD you just created as the machine's OS disk.&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-e6a1be02-b847-4bf4-9496-f9562a44ccf5.png" border="0" loading="lazy"&gt;&lt;/p&gt;
&lt;h4&gt;Install Hyper-V Integration Service&lt;/h4&gt;
&lt;p&gt;To ensure full functionality after installing Windows XP on a guest VM, it's necessary to install Hyper-V Integration Services. However, on a Windows 11 host machine, this process isn't straightforward as Microsoft has discontinued the integration service image and the associated menu options. Fortunately, the required&amp;nbsp;&lt;code&gt;vmguest.iso&lt;/code&gt;&amp;nbsp;file is available for download at the Internet Archive. You can access it via this link:&amp;nbsp;&lt;a href="https://archive.org/details/Hyper-V_vmguest" target="_blank" rel="noopener"&gt;https://archive.org/details/Hyper-V_vmguest&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Reboot the VM after installation complete. You should see all Hyper-V services running as automatic startup.&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-b2c1471b-a4ea-4c61-bd67-5966899b1271.png" border="0" loading="lazy"&gt;&lt;/p&gt;
&lt;h4&gt;Enable Remote Desktop&lt;/h4&gt;
&lt;p&gt;Go to system properties and allow remote connections. Make sure your account is added into remote users.&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-dec753e7-2b24-4d3b-983a-575518bbdfa5.png" border="0" loading="lazy"&gt;&lt;/p&gt;
&lt;h4&gt;DO NOT run sysprep&lt;/h4&gt;
&lt;p&gt;I know many of you consider &lt;strong&gt;sysprep &lt;/strong&gt;before making a generalize OS image, but this step is not required this time, If you run sysprep, your VM will blow up sky high on Azure. Just shut down the VM as normal now.&lt;/p&gt;
&lt;h2&gt;Upload and Prepare OS Disk for Azure&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;Create a storage account on Azure with a private container, e.g. "vhdstore"&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-5d70cf42-6d13-464e-b62b-1ae770474597.png" border="0" loading="lazy"&gt;&lt;/p&gt;
&lt;p&gt;Use Azure Storage Explorer or any other tools you like to upload the VHD file from your local machine to this storage container.&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-3cb30669-f926-4875-8f39-5991214f8065.png" border="0" loading="lazy"&gt;&lt;/p&gt;
&lt;p&gt;Then, from Azure CLI (on your local machine or from Azure Portal), we need to create a disk based on this VHD.&lt;/p&gt;
&lt;p&gt;Replace the values in these commands for your own values.&lt;/p&gt;
&lt;pre class="language-powershell"&gt;&lt;code&gt;az account set --subscription "DevTest"
az disk create --resource-group "devtest-group" --name "WINXPOS" --location "japaneast" --os-type Windows --source "https://devtestjpe.blob.core.windows.net/vhdstore/WINXPOS.vhd" --size-gb "4"&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src="/image/img-7c90ee3b-01d1-4511-b6bc-261d9dec99c2.png" border="0" loading="lazy"&gt;&lt;/p&gt;
&lt;p&gt;Once the Azure Disk is created, you can delete the VHD from your storage account to save money.&lt;/p&gt;
&lt;h2&gt;Create VM on Azure&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;Go to Azure portal, open the resource group you created the disk in, you should see it in the list.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-b0c6ca49-0990-449d-a54c-636d2ed9599a.png" border="0" loading="lazy"&gt;&lt;/p&gt;
&lt;p&gt;Click this disk, and click "&lt;strong&gt;Create VM&lt;/strong&gt;" button.&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-5aa64d75-9bc8-44ca-877f-30c46fd1e7d4.png" border="0" loading="lazy"&gt;&lt;/p&gt;
&lt;p&gt;Select "No infrastructure redundancy required", and choose a "Standard_B1s" size. Remember, this is Windows XP 32 bit, it won't recognize RAM larger than 4GB. 1GB is enough for XP to run very smoothly.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Please note, although the VM architecture says x64, it doesn't matter, our 32 bit system can still boot up.&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-b55bc7ac-1e13-4eaa-870f-d9adb572b9db.png" border="0" loading="lazy"&gt;&lt;/p&gt;
&lt;p&gt;Enable RDP port 3389, and choose "Windows client" in license type.&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-8720cddc-6273-4d88-b4cc-c5975311b718.png" border="0" loading="lazy"&gt;&lt;/p&gt;
&lt;p&gt;Then, create the VM as normal.&lt;/p&gt;
&lt;p&gt;After the VM is created, you can RDP into it using its public IP address as normal.&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-8def7155-ffc1-47b6-be94-db7d5d836f54.png" border="0" loading="lazy"&gt;&lt;/p&gt;
&lt;p&gt;Yeah, this is our Windows XP VM running on today's Azure!&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-7900c306-930a-4d86-a7df-c51efe979b3e.png" border="0" loading="lazy"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-7ea3b695-c2d0-4826-b07a-8c4ed0107b72.png" border="0" loading="lazy"&gt;&lt;/p&gt;
&lt;h2&gt;How about Windows 2000&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;I also managed to have Windows 2000 VM running in Azure. Just a little bit complex than running XP.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Windows 2000 Server or Advanced Server is required, because only server OS has Terminal Service (Remote Desktop)&lt;/li&gt;
&lt;li&gt;Hyper-V Integration Service is a little bit different. You must use an older version of the vmguest.iso from Windows Server 2008 R2. You can find it here: &lt;a href="https://archive.org/download/hyper-v_vmguest_2008_2008R2_2012R2" target="_blank" rel="noopener"&gt;https://archive.org/download/hyper-v_vmguest_2008_2008R2_2012R2&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="/image/img-921eb169-6888-4ac7-9943-4c88f875e64b.png" border="0" loading="lazy"&gt;&lt;/p&gt;</content></entry><entry><id>94754af1-8cf2-4600-a4d7-bab8dc0df76d</id><title>PowerShell to Enable SQL Server Express Remote Access</title><updated>2024-01-18T05:52:41Z</updated><published>2024-01-18T05:52:41Z</published><link href="https://edi.wang/post/2024/1/18/powershell-to-enable-sql-server-express-remote-access" hreflang="en-us" /><author><name>Edi Wang</name><email>Edi.Wang@outlook.com</email></author><category term="Server and Tools" /><category term="Data Platform" /><content>&lt;h2&gt;Problem&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;SQL Server Express is a free edition of Microsoft's SQL Server, which is a relational database management system (RDBMS). It is designed for small-scale applications and for users who are getting started with learning about databases and SQL Server. However, remote access is not enabled by default on this SKU. To access a SQL Server Express instance from the network, we have to do a few steps. There's plenty of guide on the internet telling you how to enable remote access via GUI, but I would like to do it from PowerShell, so that I can integrate those steps in automated environment.&lt;/p&gt;
&lt;h2&gt;Solution&lt;/h2&gt;
&lt;hr&gt;
&lt;h3&gt;Steps&lt;/h3&gt;
&lt;p&gt;To enable remote access for SQL Server Express, the general steps are&lt;/p&gt;
&lt;p&gt;Check "Allow remote connections to this server" checkbox (Already checked in a new SQL Server 2022 Express installation)&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Enable TCP/IP Protocol&lt;/li&gt;
&lt;li&gt;Set TCP port to 1433&lt;/li&gt;
&lt;li&gt;Set Windows Firewall to allow TCP 1433&lt;/li&gt;
&lt;li&gt;Restart related services&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;You can refer to a GUI guide here:&amp;nbsp;&lt;a href="https://www.apesoftware.com/calibration-control/help/sql-remote-connections" target="_blank" rel="noopener"&gt;https://www.apesoftware.com/calibration-control/help/sql-remote-connections&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;PowerShell&lt;/h3&gt;
&lt;p&gt;Thanks to &lt;a href="https://learn.microsoft.com/en-us/azure/ai-services/openai/overview?WT.mc_id=AZ-MVP-5002809" target="_blank" rel="noopener"&gt;Azure Open AI&lt;/a&gt;, I don't have to &lt;a href="https://996.icu" target="_blank" rel="noopener"&gt;work 996&lt;/a&gt; this time to make the PowerShell script. The script generally is automating the steps above.&lt;/p&gt;
&lt;pre class="language-powershell"&gt;&lt;code&gt;# Run this script as an Administrator

# Step 1: Set SQL Server Browser service to start automatically and start the service
Write-Host "Setting SQL Server Browser service to Automatic and starting the service..."
Set-Service 'SQLBrowser' -StartupType Automatic
Start-Service 'SQLBrowser'

# Step 2: Enable TCP/IP protocol for SQL Server
# Note: This step requires SQL Server Configuration Manager to be installed.
Write-Host "Enabling TCP/IP protocol for SQL Server..."
$sqlServerConfigManager = Get-WmiObject -Namespace "root\Microsoft\SqlServer\ComputerManagement16" -Class "ServerNetworkProtocol"
$tcpIp = $sqlServerConfigManager | Where-Object { $_.InstanceName -eq 'SQLEXPRESS' -and $_.ProtocolName -eq 'Tcp' }
$tcpIp.SetEnable()

# Step 3: Set the TCP Port for IPAll to 1433
Write-Host "Setting the TCP Port for IPAll to 1433..."
$regPath = "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\MSSQL16.SQLEXPRESS\MSSQLServer\SuperSocketNetLib\Tcp\IPAll"
Set-ItemProperty -Path $regPath -Name TcpPort -Value "1433"
Set-ItemProperty -Path $regPath -Name TcpDynamicPorts -Value ""

# Step 4: Create a firewall rule to allow TCP port 1433
Write-Host "Creating a firewall rule for TCP port 1433..."
New-NetFirewallRule -DisplayName "SQL Server Remote Access" -Direction Inbound -Protocol TCP -LocalPort 1433 -Action Allow

Write-Host "SQL Server Express should now accept remote connections."

Restart-Service 'MSSQL$SQLEXPRESS'
Restart-Service 'SQLBrowser'&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Please note, this script is for SQL Server 2022 Express, with default instance name "SQLExpress". If you are targeting other version of SQL Server, you should change these items in the script.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ComputerManagement16&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;MSSQL16.SQLEXPRESS&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SQLEXPRESS&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To connect to your SQL Server Express instance via network. There are also a few things to keep in mind.&lt;/p&gt;
&lt;ul&gt;
&lt;li style="text-align: left;"&gt;Add port number in server name, that is "&lt;code&gt;IP\SQLExpress,1433&lt;/code&gt;", NOT "&lt;code&gt;IP\SQLExpress&lt;/code&gt;".&lt;/li&gt;
&lt;li style="text-align: left;"&gt;Trust server certificate if SSMS throw error about certificate. &lt;br&gt;&lt;img src="/image/img-c256787d-f316-49e8-b04d-fe90a1f2945a.png" border="0" loading="lazy"&gt;&lt;/li&gt;
&lt;/ul&gt;</content></entry><entry><id>e77e2fb0-6b06-41c5-af14-8987cec7d1a7</id><title>Deploy Public DNS Server on Microsoft Azure</title><updated>2023-06-10T02:51:29Z</updated><published>2023-06-10T02:51:29Z</published><link href="https://edi.wang/post/2023/6/10/deploy-public-dns-server-on-microsoft-azure" hreflang="en-us" /><author><name>Edi Wang</name><email>Edi.Wang@outlook.com</email></author><category term="Server and Tools" /><category term="Microsoft Azure" /><content>&lt;h2&gt;Intro&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;Deploying a public DNS server on Microsoft Azure can be a crucial step for organizations and individuals that want to provide DNS resolution to the public. Deploying a public DNS server on Azure can provide benefits such as scalability, high availability, and global reach. In this blog post, we will discuss the steps involved in deploying a public DNS server on Microsoft Azure.&lt;/p&gt;
&lt;p&gt;My objective is to set up a DNS server that functions as a relay, utilizing forwarders to serve as an intermediary for client DNS queries to other public DNS servers, such as Microsoft or Google. Users can then set DNS IP address on their devices to this server. No custom domains are involved here. This is typically used in China where a lot of well-known DNS servers could be affected by some magic of the Chinese internet.&amp;nbsp;&lt;/p&gt;
&lt;h2&gt;Notice&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;Kindly note that the topic of discussion in this post does not pertain to Azure DNS Zones. Azure DNS Zones serve a different purpose, primarily when you have a custom domain name, and you need to add records on the Azure Portal. Users are not required to modify DNS IP addresses on their devices. For instance, my blog uses Azure DNS Zones, but users don't have to alter their network configurations to access it. Therefore, if you are seeking information about Azure DNS Zones, this is not the appropriate post for you.&lt;/p&gt;
&lt;h2&gt;Platforms&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;We will be using VMs to deploy DNS services. We will not be covering the steps involved in creating the VMs themselves. Instead, we will be focusing on the key steps and configurations required to deploy the DNS services on the VMs.&lt;/p&gt;
&lt;h2&gt;Network Security Group configuration&lt;/h2&gt;
&lt;p&gt;No matter what type of OS you choose. NSG must be configured to open both &lt;strong&gt;TCP/53&lt;/strong&gt; and &lt;strong&gt;UDP/53&lt;/strong&gt; ports.&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-5a19fa2b-3c5c-46b3-928b-eeda19201d64.png" border="0" loading="lazy"&gt;&lt;/p&gt;
&lt;h3&gt;Static IP Address&lt;/h3&gt;
&lt;p&gt;Make sure your VM is bound with a static public IP address.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-6bda8a9c-95be-4bb8-bc59-2a99b1e623e9.png" border="0" loading="lazy"&gt;&lt;/p&gt;
&lt;h3&gt;Windows Server 2022 Server Core&lt;/h3&gt;
&lt;p&gt;Go to PowerShell and run:&lt;/p&gt;
&lt;pre class="language-powershell"&gt;&lt;code&gt;Install-WindowsFeature -Name DNS -IncludeManagementTools&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src="/image/img-fa1ac6ac-a736-4851-aba7-eaefac402eb4.png" border="0" loading="lazy"&gt;&lt;/p&gt;
&lt;p&gt;Then, add forwarders. I am using Google's DNS server IP address as forwarders.&lt;/p&gt;
&lt;p&gt;DNS forwarders are used to allow a DNS server to forward DNS queries to another DNS server, typically one that is authoritative for a particular domain. This is useful in situations where the DNS server is not authoritative for the requested domain, or when the DNS server is unable to resolve the query locally. By forwarding the query to another DNS server, the DNS server can obtain the necessary information to resolve the query and return the result to the client. This can improve DNS performance and reduce network traffic, as the DNS server does not need to perform recursive queries for every request.&lt;/p&gt;
&lt;pre class="language-powershell"&gt;&lt;code&gt;Add-DnsServerForwarder -IPAddress 8.8.8.8 -PassThru
Add-DnsServerForwarder -IPAddress 8.8.4.4 -PassThru&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src="/image/img-8a6fc5ae-3940-40cb-aeea-0523b016ad79.png" border="0" loading="lazy"&gt;&lt;/p&gt;
&lt;p&gt;Now, on your client device, set DNS server IP to this server, and run &lt;code&gt;nslookup &lt;/code&gt;to check if DNS records can be resolved correctly.&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-ce8c31a8-fb01-44a9-adb2-30134fa977ed.png" border="0" loading="lazy"&gt;&lt;/p&gt;
&lt;h3&gt;Ubuntu Server&lt;/h3&gt;
&lt;p&gt;SSH into your VM and run&lt;/p&gt;
&lt;pre class="language-bash"&gt;&lt;code&gt;sudo apt update &amp;amp;&amp;amp; sudo apt upgrade -y
sudo apt install bind9 -y&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you are using a minimized Ubuntu Server image, you may also need to install a text editor like nano&lt;/p&gt;
&lt;pre class="language-bash"&gt;&lt;code&gt;sudo apt install nano -y&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Use nano to open bind9 configuration file&lt;/p&gt;
&lt;pre class="language-bash"&gt;&lt;code&gt;sudo nano /etc/bind/named.conf.options&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Add forwarders to Google DNS server as an example. Please note, we must also add &lt;code&gt;allow-query-cache { any; };&lt;/code&gt;&lt;/p&gt;
&lt;pre class="language-bash"&gt;&lt;code&gt;forwarders {
    8.8.8.8;
    8.8.4.4;
};

allow-query-cache { any; };&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src="/image/img-924cd9c0-e01e-4f06-b17b-fa7cf34f0063.png" border="0" loading="lazy"&gt;&lt;/p&gt;
&lt;p&gt;Save the configuration file, then restart bind9 services.&lt;/p&gt;
&lt;pre class="language-bash"&gt;&lt;code&gt;sudo systemctl restart bind9&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Test the DNS server on your client device.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-ab3836dc-bc6a-46d7-9d8b-fa7e6683fd8e.png" border="0" loading="lazy"&gt;&lt;/p&gt;</content></entry><entry><id>4263343c-f83d-451e-ad61-b44ec50e5a75</id><title>Setup Hyper-V Server 2019 in My Home PC</title><updated>2023-05-06T02:15:52Z</updated><published>2023-05-06T02:15:52Z</published><link href="https://edi.wang/post/2023/5/6/setup-hyperv-server-2019-in-my-home-pc" hreflang="en-us" /><author><name>Edi Wang</name><email>Edi.Wang@outlook.com</email></author><category term="Server and Tools" /><content>&lt;h2&gt;Background&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;I recently acquired an HP 800G2 PC from my company for just 200 RMB. I decided to utilize it to host VMs for my tech experiments, as running VMs on my home LAN provides me with advantages over remote connections to Azure datacenters, which I typically use.&lt;/p&gt;
&lt;p&gt;After installing Hyper-V Server 2019 on the machine, I deployed an Ubuntu Server VM and a Windows 10 VM. In this post, I will share the process and tips for setting up this kind of environment.&lt;/p&gt;
&lt;h2&gt;Hyper-V Server, Windows Server with Hyper-V Role, or VMWare Esxi&lt;/h2&gt;
&lt;hr&gt;
&lt;h3&gt;Type 1 or Type 2 Hypervisor&lt;/h3&gt;
&lt;p&gt;I spent some time studying and making the choice among the host OS. First, there are two types of hypervisors. Type 1 and Type 2. All three products, Hyper-V Server, Windows Server with Hyper-V Role, and Esxi are Type 1 hypervisor. VMWare workstation on the other hand, is Type 2 hypervisor.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Type 1 hypervisor runs directly on the host machine's hardware, while type 2 hypervisor runs on top of an operating system. Type 1 hypervisors are often referred to as bare-metal hypervisors because they operate directly on the host machine's underlying hardware. They are typically used in enterprise-level virtualization environments because of their speed and security. Type 2 hypervisors, on the other hand, are installed on top of an existing operating system and are primarily used for desktop virtualization. They are slower than type 1 hypervisors and less secure but easier to use and install.&lt;/p&gt;
&lt;p&gt;There is no difference between VMs running on Hyper-V Server vs Windows Server with Hyper-V Role. Hyper-V Server is a Windows Server Core with Hyper-V Role pre-installed. They are both type 1 hypervisors. The only diffrence is that Hyper-V Server has some other roles removed from the OS, Windows Server has a full list of roles, which in this case, you won't need them.&lt;/p&gt;
&lt;p&gt;As stated in the book "Windows Internals", when you install Hyper-V for a Windows Server, or even in Windows 10/11, the host OS itself will also be running on hypervisor just like a VM. So, for anyone that is concerned if Hyper-V Server has better performance than Windows Server with Hyper-V Role, this is false. THEY ARE SAME! VM all runs as Type 1 hypervisor.&lt;/p&gt;
&lt;h3&gt;Free or Paid&lt;/h3&gt;
&lt;p&gt;Hyper-V Server 2019: Free&lt;/p&gt;
&lt;p&gt;Windows Server 2022 with Hyper-V Role: Paid&lt;/p&gt;
&lt;p&gt;VMWare Esxi: Can be free&lt;/p&gt;
&lt;h3&gt;Last version of Hyper-V Server&lt;/h3&gt;
&lt;p&gt;Hyper-V Server 2019 is the last version of Hyper-V Server and continues to be supported until 2029. There won't be a Hyper-V Server 2022 or later. Microsoft recommends using Azure Stack HCI, which is also a commercial product. As my goal is not to pay a cent for OS on a 200RMB low end machine, I won't use Azure Stack HCI.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;If you really like Hyper-V Server and hate Azure. You can choose Windows Server Core and install Hyper-V Role (but you must pay for it). As described above, there is no diffrence for VMs.&lt;/p&gt;
&lt;h2&gt;Install Hyper-V Server 2019&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;Installing Hyper-V Server 2019 is no diffrence from a typical installation of Windows Server Core. I use Rufus (&lt;a href="https://rufus.ie/en/"&gt;https://rufus.ie/en/&lt;/a&gt;)to burn ISO file into a USB drive and install on the machine.&lt;/p&gt;
&lt;h3&gt;Initial Configuration&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Computer Name&lt;/strong&gt;: This is for remote managing your machine in later step. IP address can't be used for today's Windows management tools. So, you must give the server a name.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Network&lt;/strong&gt;: Required for remote management&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Remote Desktop&lt;/strong&gt;: Required for remote management&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Configure Remote Management&lt;/strong&gt;: Required for Windows Admin Center and Hyper-V Manager to access remotely&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Date and Time&lt;/strong&gt;: Required for OS to work properly&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Download and Install Updates&lt;/strong&gt;: Make sure your hardware drivers got installed and your OS got latest patch&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-cdeb208d-188b-4048-96ce-8ef8ca214e09.png" border="0" loading="lazy"&gt;&lt;/p&gt;
&lt;h3&gt;Enable Remote Management on Client Machine&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;This is the most important setup in the entire process&lt;/strong&gt;. If blow up, please watch &lt;a href="https://www.youtube.com/watch?v=ZPjtiXB5k5s"&gt;https://www.youtube.com/watch?v=ZPjtiXB5k5s&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;On your laptop, or other machine that you will use to manage this server. Run these commands in PowerShell with Admin. Replace the host name, IP, network name, and password for your own values.&lt;/p&gt;
&lt;pre class="language-powershell"&gt;&lt;code&gt;Set-Item WSMan:\localhost\Client\TrustedHosts -Value "EDI-800G2HVS"

# Add server IP to host file
Add-Content -Path "C:\Windows\System32\drivers\etc\hosts" -Value "192.168.0.102 EDI-800G2HVS"

# Get current network profile name for next command
Get-NetConnectionProfile

# Set as Private to allow connection
Set-NetConnectionProfile -InterfaceAlias "Wi-Fi" -NetworkCategory Private

Enable-PSRemoting
Get-WSManCredSSP
Enable-WSManCredSSP -Role Client -DelegateComputer "EDI-800G2HVS"

cmdkey /add:EDI-800G2HVS /user: Administrator /pass:*******&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Hyper-V Management&lt;/h3&gt;
&lt;p&gt;Run &lt;code&gt;optionalfeatures &lt;/code&gt;and Install only the Hyper-V Management Tools on your client machine.&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-7f0bde05-cabb-410c-96e6-785a4e753038.png" border="0" loading="lazy"&gt;&lt;/p&gt;
&lt;p&gt;You will now be able to add your server and run VMs on it.&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-20e551e8-d74c-4288-9462-33497d5246bc.png" border="0" loading="lazy"&gt;&lt;/p&gt;
&lt;h3&gt;Windows Admin Center&lt;/h3&gt;
&lt;p&gt;For a complete remote management capability, install Windows Admin Center (&lt;a href="https://www.microsoft.com/en-us/windows-server/windows-admin-center"&gt;https://www.microsoft.com/en-us/windows-server/windows-admin-center&lt;/a&gt;) on your client machine, then connect to your server.&lt;/p&gt;
&lt;p&gt;For example, you can easily make file shares to transfer VM OS install ISO files.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-4cb6b3fc-b9c7-4196-9de2-7639de721a26.png" border="0" loading="lazy"&gt;&lt;/p&gt;</content></entry><entry><id>0b86b703-c259-4b9d-b745-b0b4969b12cd</id><title>Testing Network Speed Between Azure China and Azure Global for B1s VM</title><updated>2023-02-25T10:15:21Z</updated><published>2023-02-25T10:15:21Z</published><link href="https://edi.wang/post/2023/2/25/testing-network-speed-between-azure-china-and-azure-global-for-b1s-vm" hreflang="en-us" /><author><name>Edi Wang</name><email>Edi.Wang@outlook.com</email></author><category term="Server and Tools" /><category term="Microsoft Azure" /><content>&lt;h2&gt;Background&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;Some businesses choose to deploy their systems across both Azure China and Azure Global. For these systems to function effectively, it is crucial that their communication over the internet performs well. To assess the current state of the network between Azure China and Azure Global, I conducted a series of tests and would like to share the results.&lt;/p&gt;
&lt;p dir="auto"&gt;&lt;strong&gt;Targeted:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Azure Virtual Machines with the B1s size&lt;/li&gt;
&lt;/ul&gt;
&lt;p dir="auto"&gt;&lt;strong&gt;Not Targeted:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Azure Virtual Machines with accelerated networking (typically B12ms, B16ms, B20ms, or F-series)&lt;/li&gt;
&lt;li&gt;Azure ExpressRoute (primarily an option for large enterprises)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The goal of these tests is not to achieve the highest possible network speed or to promote Azure. This is why I intentionally avoided using high-end VM and network configurations. My aim is to provide a realistic representation of what individuals and small businesses in China typically experience.&lt;/p&gt;
&lt;h2&gt;Test Method&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;I use iPerf3 to perform the tests. The method is fully explained in my previous blog post "&lt;a href="/post/2020/8/4/test-network-speed-between-azure-vms-using-iperf" target="_blank" rel="noopener"&gt;Test Network Speed Between Azure VMs Using iPerf&lt;/a&gt;".&amp;nbsp;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;VM configuration
&lt;ul&gt;
&lt;li&gt;System: Ubuntu Server 22.04 LTS&lt;/li&gt;
&lt;li&gt;Size: B1s&lt;/li&gt;
&lt;li&gt;Disk: Standard SSD&lt;/li&gt;
&lt;li&gt;NSG: Allow TCP 5201 port&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Azure China Regions used
&lt;ul&gt;
&lt;li&gt;China East 2&lt;/li&gt;
&lt;li&gt;China North 2&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Azure Global Regions used&amp;nbsp;
&lt;ul&gt;
&lt;li&gt;East Asia&lt;/li&gt;
&lt;li&gt;Southeast Asia&lt;/li&gt;
&lt;li&gt;Japan East&lt;/li&gt;
&lt;li&gt;Japan West&lt;/li&gt;
&lt;li&gt;Korea Central&lt;/li&gt;
&lt;li&gt;West US&lt;/li&gt;
&lt;li&gt;East US&lt;/li&gt;
&lt;li&gt;Australia East&lt;/li&gt;
&lt;li&gt;West Europe&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;3 test runs for each VMs from Azure China to Azure Global&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Cloud init content&lt;/p&gt;
&lt;pre class="language-bash"&gt;&lt;code&gt;#cloud-config
runcmd:
  - apt update &amp;amp;&amp;amp; apt upgrade -y
  - apt install iperf3 -y&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ubuntu Server is using the following TCP congestion control&lt;/p&gt;
&lt;pre class="language-bash"&gt;&lt;code&gt;edi@****:~$ sysctl net.ipv4.tcp_available_congestion_control
net.ipv4.tcp_available_congestion_control = reno cubic bbr
edi@****:~$ sysctl net.ipv4.tcp_congestion_control
net.ipv4.tcp_congestion_control = bbr&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Keep in mind, &lt;strong&gt;these tests only represent results in &lt;span style="color: #e03e2d;"&gt;Feb 2023&lt;/span&gt;&lt;/strong&gt;. Microsoft could change the network configuration any time. So, these test results are only for your reference. Please do your own test whenever you need.&lt;/p&gt;
&lt;h2&gt;About Azure's Network Bandwidth Limit&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;You can find expected network bandwidth for varied sizes of VMs &lt;a href="https://learn.microsoft.com/en-us/azure/virtual-machines/sizes?WT.mc_id=AZ-MVP-5002809" target="_blank" rel="noopener"&gt;here&lt;/a&gt;. Most series have a table with networking specifications in the last column titled, Max NICs / Expected network performance (Mbps).&lt;/p&gt;
&lt;p&gt;However, for &lt;span style="background-color: #fbeeb8;"&gt;&lt;strong&gt;B series VMs, including the B1s I used in these tests, does not have information about network bandwidth limit&lt;/strong&gt;&lt;/span&gt;. According to information from Microsoft product team, &lt;span style="background-color: #fbeeb8;"&gt;&lt;strong&gt;B series does not provide a consistent level of network performance&lt;/strong&gt;&lt;/span&gt;, this is why they don't publish network bandwidth limit information about it. This is also one of the reasons why I am writing this post.&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-38d2e72a-6758-483c-8156-06d4e3b4d069.png" border="0" loading="lazy"&gt;&lt;/p&gt;
&lt;h2&gt;Test Example&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;I am doing the same process for each DC location, to keep this post short and clean, I do not post every test run output here. You can find the information in the "Conclusion" section of this post.&lt;/p&gt;
&lt;h3&gt;Datacenter: China East 2 - East Asia&lt;/h3&gt;
&lt;p&gt;Note: Sensitive host name and IP address information is masked as `*`.&lt;/p&gt;
&lt;h4&gt;Test run #1&lt;/h4&gt;
&lt;pre class="language-bash"&gt;&lt;code&gt;edi@****:~$ iperf3 -c *.*.*.* -p 5201 -t 10
Connecting to host *.*.*.*, port 5201
[  5] local 10.1.0.4 port 54568 connected to *.*.*.* port 5201
[ ID] Interval           Transfer     Bitrate         Retr  Cwnd
[  5]   0.00-1.00   sec  68.0 MBytes   571 Mbits/sec    0   6.04 MBytes
[  5]   1.00-2.00   sec  87.5 MBytes   734 Mbits/sec    0   6.04 MBytes
[  5]   2.00-3.00   sec  86.2 MBytes   723 Mbits/sec    0   6.04 MBytes
[  5]   3.00-4.00   sec  87.5 MBytes   734 Mbits/sec    0   6.04 MBytes
[  5]   4.00-5.00   sec  87.5 MBytes   734 Mbits/sec    0   6.04 MBytes
[  5]   5.00-6.00   sec  86.2 MBytes   723 Mbits/sec    0   6.04 MBytes
[  5]   6.00-7.00   sec  87.5 MBytes   734 Mbits/sec    0   6.04 MBytes
[  5]   7.00-8.00   sec  87.5 MBytes   734 Mbits/sec    0   6.04 MBytes
[  5]   8.00-9.00   sec  87.5 MBytes   734 Mbits/sec    0   6.04 MBytes
[  5]   9.00-10.00  sec  87.5 MBytes   734 Mbits/sec    0   6.04 MBytes
- - - - - - - - - - - - - - - - - - - - - - - - -
[ ID] Interval           Transfer     Bitrate         Retr
[  5]   0.00-10.00  sec   853 MBytes   716 Mbits/sec    0             sender
[  5]   0.00-10.06  sec   852 MBytes   711 Mbits/sec                  receiver&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Test run #2&lt;/h4&gt;
&lt;pre class="language-bash"&gt;&lt;code&gt;edi@****:~$ iperf3 -c *.*.*.* -p 5201 -t 10
Connecting to host *.*.*.*, port 5201
[  5] local 10.1.0.4 port 54568 connected to *.*.*.* port 5201
[ ID] Interval           Transfer     Bitrate         Retr  Cwnd
[  5]   0.00-1.00   sec  67.1 MBytes   563 Mbits/sec    0   6.01 MBytes
[  5]   1.00-2.00   sec  86.2 MBytes   723 Mbits/sec    0   6.01 MBytes
[  5]   2.00-3.00   sec  85.0 MBytes   713 Mbits/sec    0   6.01 MBytes
[  5]   3.00-4.00   sec  85.0 MBytes   713 Mbits/sec    0   6.01 MBytes
[  5]   4.00-5.00   sec  86.2 MBytes   724 Mbits/sec    0   6.01 MBytes
[  5]   5.00-6.00   sec  86.2 MBytes   723 Mbits/sec    0   6.01 MBytes
[  5]   6.00-7.00   sec  85.0 MBytes   713 Mbits/sec    0   6.01 MBytes
[  5]   7.00-8.00   sec  85.0 MBytes   713 Mbits/sec    0   6.01 MBytes
[  5]   8.00-9.00   sec  85.0 MBytes   713 Mbits/sec    0   6.01 MBytes
[  5]   9.00-10.00  sec  85.0 MBytes   713 Mbits/sec    0   6.01 MBytes
- - - - - - - - - - - - - - - - - - - - - - - - -
[ ID] Interval           Transfer     Bitrate         Retr
[  5]   0.00-10.00  sec   836 MBytes   701 Mbits/sec    0             sender
[  5]   0.00-10.04  sec   835 MBytes   698 Mbits/sec                  receiver&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Test run #3&lt;/h4&gt;
&lt;pre class="language-bash"&gt;&lt;code&gt;edi@****:~$ iperf3 -c *.*.*.* -p 5201 -t 10
Connecting to host *.*.*.*, port 5201
[  5] local 10.1.0.4 port 54568 connected to *.*.*.* port 5201
[ ID] Interval           Transfer     Bitrate         Retr  Cwnd
[  5]   0.00-1.00   sec  66.8 MBytes   560 Mbits/sec    0   6.06 MBytes
[  5]   1.00-2.00   sec  85.0 MBytes   713 Mbits/sec    0   6.06 MBytes
[  5]   2.00-3.00   sec  85.0 MBytes   713 Mbits/sec    0   6.06 MBytes
[  5]   3.00-4.00   sec  83.8 MBytes   703 Mbits/sec    0   6.06 MBytes
[  5]   4.00-5.00   sec  85.0 MBytes   713 Mbits/sec    0   6.06 MBytes
[  5]   5.00-6.00   sec  83.8 MBytes   703 Mbits/sec    0   6.06 MBytes
[  5]   6.00-7.00   sec  85.0 MBytes   713 Mbits/sec    0   6.06 MBytes
[  5]   7.00-8.00   sec  83.8 MBytes   703 Mbits/sec    0   6.06 MBytes
[  5]   8.00-9.00   sec  83.8 MBytes   703 Mbits/sec    0   6.06 MBytes
[  5]   9.00-10.00  sec  85.0 MBytes   713 Mbits/sec    0   6.06 MBytes
- - - - - - - - - - - - - - - - - - - - - - - - -
[ ID] Interval           Transfer     Bitrate         Retr
[  5]   0.00-10.00  sec   827 MBytes   694 Mbits/sec    0             sender
[  5]   0.00-10.04  sec   827 MBytes   690 Mbits/sec                  receiver&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Average: 702 Mbps&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li&gt;As for &lt;span style="background-color: #fbeeb8;"&gt;2/25/2023&lt;/span&gt;, the network speed for B1s VMs between a few datacenters in Azure China and Azure Global are listed below.&lt;/li&gt;
&lt;li&gt;Azure does not provide an official bandwidth limit information for B1s, it seems to be 1Gbps.&lt;/li&gt;
&lt;li&gt;The fastest connection is between &lt;strong&gt;China East 2&lt;/strong&gt; in Azure China and &lt;strong&gt;East Asia&lt;/strong&gt; in Azure Global.&amp;nbsp;&lt;/li&gt;
&lt;/ol&gt;
&lt;table class="table table-bordered"&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Source location&lt;/th&gt;
&lt;th&gt;Target location&lt;/th&gt;
&lt;th&gt;Average speed&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;China East 2&lt;/td&gt;
&lt;td&gt;East Asia&lt;/td&gt;
&lt;td&gt;702 Mbps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;China East 2&lt;/td&gt;
&lt;td&gt;Southeast Asia&lt;/td&gt;
&lt;td&gt;357 Mbps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;China East 2&lt;/td&gt;
&lt;td&gt;Japan East&lt;/td&gt;
&lt;td&gt;272 Mbps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;China East 2&lt;/td&gt;
&lt;td&gt;Japan West&lt;/td&gt;
&lt;td&gt;275 Mbps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;China East 2&lt;/td&gt;
&lt;td&gt;Korea Central&lt;/td&gt;
&lt;td&gt;297 Mbps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;China East 2&lt;/td&gt;
&lt;td&gt;West US&lt;/td&gt;
&lt;td&gt;125 Mbps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;China East 2&lt;/td&gt;
&lt;td&gt;East US&lt;/td&gt;
&lt;td&gt;65 Mbps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;China East 2&lt;/td&gt;
&lt;td&gt;Australia East&lt;/td&gt;
&lt;td&gt;112 Mbps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;China East 2&lt;/td&gt;
&lt;td&gt;West Europe&lt;/td&gt;
&lt;td&gt;73 Mbps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;China North 2&lt;/td&gt;
&lt;td&gt;East Asia&lt;/td&gt;
&lt;td&gt;564 Mbps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;China North 2&lt;/td&gt;
&lt;td&gt;Southeast Asia&lt;/td&gt;
&lt;td&gt;276 Mbps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;China North 2&lt;/td&gt;
&lt;td&gt;Japan East&lt;/td&gt;
&lt;td&gt;268 Mbps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;China North 2&lt;/td&gt;
&lt;td&gt;Japan West&lt;/td&gt;
&lt;td&gt;261 Mbps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;China North 2&lt;/td&gt;
&lt;td&gt;Korea Central&lt;/td&gt;
&lt;td&gt;296 Mbps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;China North 2&lt;/td&gt;
&lt;td&gt;West US&lt;/td&gt;
&lt;td&gt;108 Mbps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;China North 2&lt;/td&gt;
&lt;td&gt;East US&lt;/td&gt;
&lt;td&gt;78 Mbps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;China North 2&lt;/td&gt;
&lt;td&gt;Australia East&lt;/td&gt;
&lt;td&gt;126 Mbps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;China North 2&lt;/td&gt;
&lt;td&gt;West Europe&lt;/td&gt;
&lt;td&gt;86 Mbps&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;</content></entry><entry><id>ab9266f8-d881-42c9-b2f3-1611ca65e027</id><title>How to Print Borderless Photos on Cannon MG2580s</title><updated>2021-10-07T02:59:58Z</updated><published>2021-10-07T02:59:58Z</published><link href="https://edi.wang/post/2021/10/7/how-to-print-borderless-photos-on-cannon-mg2580s" hreflang="en-us" /><author><name>Edi Wang</name><email>Edi.Wang@outlook.com</email></author><category term="Server and Tools" /><content>&lt;p&gt;I have a Cannon MG2580s printer, it can print 4x6 inch (10x15cm) A6 size photos, but with ugly white borders even the printing software is set to borderless. This is because this model of printer, like some other models in the MG2500 series family, does not support borderless printing on hardware level, it's not a software or settings issue.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;I've come up with a workaround that can produce borderless printing for 4x6 inch photos on my Cannon MG2580s printer.&lt;/p&gt;
&lt;h2&gt;Prepare a Word template&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;First, we'll use Microsoft Word (or other application) to make a paper size that is larger than 4x6 inch, which should be exactly &lt;strong&gt;109x171 mm or 4.29x6.73 inch&lt;/strong&gt;. You can insert a rectangle of that size in Microsoft Word, and then print it on an A4 paper.&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-55fb53da-af5c-41ba-a7a4-6acfaf567ff1.png" border="0" loading="lazy"&gt;&lt;/p&gt;
&lt;p&gt;Then, cut the paper around the rectangle border, you will get a 109x171 mm paper.&lt;/p&gt;
&lt;p&gt;Remove all other papers from your printer tray and insert this into the tray.&lt;/p&gt;
&lt;h2&gt;Make a 4x6 inch image&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;Use Adobe Photoshop or other applications to make a 4x6 inch or 10.2x15.2mm image. I've made this image that you can save to your computer.&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-e114bcf8-5b80-4f53-b462-2fc0b9c39588.png" border="0" alt="" width="1800" height="1200" loading="lazy"&gt;&lt;/p&gt;
&lt;h2&gt;Set a custom page size&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;Open printer setup dialog. Select &lt;strong&gt;Photo Printing &lt;/strong&gt;and&amp;nbsp;set a &lt;strong&gt;Custom &lt;/strong&gt;page size.&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-a7f84d37-1d52-40dd-9e48-21a9db054375.png" border="0" loading="lazy"&gt;&lt;/p&gt;
&lt;p&gt;Set it to 109x171 mm&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-853e1c52-4d8d-4819-bef1-7eb2c095a55e.png" border="0" loading="lazy"&gt;&lt;/p&gt;
&lt;p&gt;Click &lt;strong&gt;Save&lt;/strong&gt;, and give our custom page size a name like 109x171-A6&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-2d874a0e-e9cd-411f-aecc-1d5e1b052655.png" border="0" loading="lazy"&gt;&lt;/p&gt;
&lt;h2&gt;Print the image&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;Now, use the custom page size to print the 4x6 inch image that we made. The software I use is IrfanView. The printing dialog is like this.&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-73d5ec77-b737-4268-a43a-faaafb2d9908.png" border="0" loading="lazy"&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-15dff0d3-0f9e-4c55-8e45-4aab9389457a.png" border="0" loading="lazy"&gt;&lt;/p&gt;
&lt;p&gt;Cut four pieces from a sticky note and glue them onto the four corners of the image, make sure the sticky side is facing you.&lt;/p&gt;
&lt;p&gt;You will now have a template like this.&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-2132aa06-7a04-4984-9628-15dcce397c0f.jpg" border="0" alt="" width="1280" height="960" loading="lazy"&gt;&lt;/p&gt;
&lt;h2&gt;Print the photo&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;Now, stick a standard 4x6 inch photo paper onto the template we made. Then, print a photo using the custom page size. You will now have a nice borderless photo!&lt;/p&gt;</content></entry><entry><id>bf1373a7-9dfc-4899-a722-1ab7d13fa07a</id><title>ASP.NET Core 5.0 Throughput Test in Kestrel, IIS, Nginx and Caddy</title><updated>2021-02-03T04:58:31Z</updated><published>2021-02-03T04:58:31Z</published><link href="https://edi.wang/post/2021/2/3/aspnet-core-50-throughput-test-in-kestrel-iis-nginx-and-caddy" hreflang="en-us" /><author><name>Edi Wang</name><email>Edi.Wang@outlook.com</email></author><category term=".NET" /><category term="Server and Tools" /><content>&lt;p&gt;Starting from version 2.2, ASP.NET Core allow you to use the InProcess mode to improve performance under IIS. Rick Strahl has &lt;a href="https://weblog.west-wind.com/posts/2019/Mar/16/ASPNET-Core-Hosting-on-IIS-with-ASPNET-Core-22" target="_blank" rel="noopener"&gt;a detailed article &lt;/a&gt;on this. Three years have passed, and now ASP.NET Core has reached version 5.0, how the performance diffs between servers? Let's take a look together.&lt;/p&gt;
&lt;h2&gt;Rick's Test Result&lt;/h2&gt;
&lt;hr /&gt;
&lt;p&gt;In the &lt;a href="https://weblog.west-wind.com/posts/2019/Mar/16/ASPNET-Core-Hosting-on-IIS-with-ASPNET-Core-22" target="_blank" rel="noopener"&gt;original article&lt;/a&gt;, Rick Strahl tested the performance of ASP.NET Core 2.2 in Kestrel, IIS InProcess, and IIS Out of Process under Windows. The metric is request per second. The conclusion was &lt;strong&gt;IIS InProcess&amp;gt; Kestrel&amp;gt; IIS Out of Process&lt;/strong&gt;.&amp;nbsp;&lt;/p&gt;
&lt;h2&gt;Today's Experiment&lt;/h2&gt;
&lt;hr /&gt;
&lt;p&gt;Rick did not test the difference between running ASP.NET Core on Windows servers and Linux servers. Many friends want to know whether Windows or Linux is good for running ASP.NET Core 5.0 in 2021, so I decided to use a similar method to re-run the ASP.NET Core 5.0 request per second metric on Windows and Linux to find out.&lt;/p&gt;
&lt;h2&gt;Testing Environment&lt;/h2&gt;
&lt;hr /&gt;
&lt;p&gt;Since Windows 10, Ubuntu Desktop and other desktop systems do not truly represent the server environment, I chose the server version of these OS for testing. All systems are freshly installed with the latest patch, and they have been rebooted once before running test.&lt;/p&gt;
&lt;h3&gt;Windows Server&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Provider&lt;/strong&gt;: Microsoft Azure East Asia Region&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;OS&lt;/strong&gt;: Windows Server 2019 Data Center&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Size&lt;/strong&gt;: B2S / 2 vCPU, 4GB RAM, Premium SSD&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Environment&lt;/strong&gt;: IIS with Static and Dynamic compression, no ASP.NET 3.5 or 4.x integration, installed ASP.NET Core 5.0.2 Runtime&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="/image/img-78a603a4-938b-4e54-880d-45563c9f2597.png" alt="" width="825" height="468" loading="lazy" /&gt;&lt;/p&gt;
&lt;h3&gt;Linux Server&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Provider&lt;/strong&gt;: Microsoft Azure East Asia Region&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;OS&lt;/strong&gt;: Ubuntu Server 20.04 LTS&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Size&lt;/strong&gt;: B2S / 2 vCPU, 4GB RAM, Premium SSD&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Environment&lt;/strong&gt;: BBR Enabled, installed Nginx, Caddy and ASP.NET Core 5.0.2 Runtime&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="/image/img-a5447d0e-7d2a-4730-8427-668c67b3a9d0.png" alt="" width="946" height="487" loading="lazy" /&gt;&lt;/p&gt;
&lt;h2&gt;Test Tool&lt;/h2&gt;
&lt;p&gt;Rick used &lt;a href="https://websurge.west-wind.com/" target="_blank" rel="noopener"&gt;West Wind Web Surge&lt;/a&gt;, but this tool is only available on the Windows platform, which cannot meet our needs. So I used &lt;a href="https://github.com/codesenberg/bombardier" target="_blank" rel="noopener"&gt;&lt;strong&gt;bombardier&lt;/strong&gt;&lt;/a&gt;, an open source, cross-platform testing tool, which was also appeared on Microsoft&amp;rsquo;s official .NET blog once.&lt;/p&gt;
&lt;h2&gt;Testing Application&lt;/h2&gt;
&lt;hr /&gt;
&lt;p&gt;I created a new ASP.NET Core 5.0 Web API project with only one method:&lt;/p&gt;
&lt;pre class="language-csharp"&gt;&lt;code&gt;[ApiController]
[Route("[controller]")]
public class TestController : ControllerBase
{
    [HttpGet]
    public string Get()
    {
        return $"Test {DateTime.UtcNow}";
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For simplicity, I will not test Json serialization and other operations this time, feel free to explore these testings for yourself.&lt;/p&gt;
&lt;p&gt;The project is compiled with Release configuration and published using FDD. Log configuration is left as default:&lt;/p&gt;
&lt;pre class="language-json"&gt;&lt;code&gt;"LogLevel": {
  "Default": "Information",
  "Microsoft": "Warning",
  "Microsoft.Hosting.Lifetime": "Information"
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Test Method&lt;/h2&gt;
&lt;hr /&gt;
&lt;p&gt;Use &lt;strong&gt;Kestrel, IIS In Process, IIS Out of Process, Nginx reverse proxy, Caddy reverse proxy&lt;/strong&gt; to run the test project, and then use &lt;strong&gt;bombardier &lt;/strong&gt;to access the localhost test address with &lt;strong&gt;2 connections, and 10 seconds duration&lt;/strong&gt;, after a warm-up round, run for 3 consecutive rounds and take the average of Request per Second data.&lt;/p&gt;
&lt;p&gt;Please note that in an ideal environment, it is best not to use a performance test tool to test the localhost address, because the operating system itself will have a certain impact on the network resources between scheduling the test tool and the Web server. But since network in the cloud environment is not so stable, in order to eliminate the network influence as much as possible, I have to visit localhost for the testing.&lt;/p&gt;
&lt;h2&gt;Test Result&lt;/h2&gt;
&lt;hr /&gt;
&lt;h3&gt;Windows + Kestrel&lt;/h3&gt;
&lt;p&gt;Avg RPS: &lt;strong&gt;18808&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-e26fa67c-4e84-4a2c-a02d-c104cefb8f54.png" alt="" width="1075" height="724" loading="lazy" /&gt;&lt;/p&gt;
&lt;h3&gt;Windows + IIS In Process&lt;/h3&gt;
&lt;p&gt;Avg RPS: &lt;strong&gt;10089&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-5f0c99e1-a0df-4200-97ca-cd060c653887.png" alt="" width="1080" height="725" loading="lazy" /&gt;&lt;/p&gt;
&lt;h3&gt;Windows + IIS Out of Process&lt;/h3&gt;
&lt;p&gt;Avg RPS: &lt;strong&gt;2820&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-783e821f-5901-4338-b620-b98fd0a6de65.png" alt="" width="1080" height="737" loading="lazy" /&gt;&lt;/p&gt;
&lt;h3&gt;Linux + Kestrel&lt;/h3&gt;
&lt;p&gt;Avg RPS: &lt;strong&gt;10667&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;img src="/image/img-ac9b4aed-c848-4655-8949-f2ee6dfac28b.png" alt="" loading="lazy" /&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;Linux + Nginx&lt;/h3&gt;
&lt;p&gt;Avg RPS: &lt;strong&gt;3509&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-60fb856d-adc2-40c5-905f-23a412fdeaa4.png" alt="" loading="lazy" /&gt;&lt;/p&gt;
&lt;h3&gt;Linux + Caddy&lt;/h3&gt;
&lt;p&gt;Avg RPS: &lt;strong&gt;3485&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-2591061f-bd16-489a-9bc7-16742189d7ab.png" alt="" loading="lazy" /&gt;&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;hr /&gt;
&lt;p&gt;Ranking of test results (from fast to slow):&lt;/p&gt;
&lt;ol class="list-paddingleft-2"&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Windows + Kestrel (18808)&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Linux + Kestrel (10667)&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Windows + IIS In Process (10089)&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Linux + Nginx (3509)&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Linux + Caddy (3485)&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Windows + IIS Out of Process (2820)&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The result is different from Rick's test for ASP.NET Core 2.2. In Rick's test, the performance of IIS In Process can surpass Kestrel. But today, IIS In Process is slammed by Kestrel, which seems quite reasonable.&lt;/p&gt;
&lt;p&gt;But what I didn't expect is that the performance of Windows server is higher than Linux with both Kestrel server. I was surprised.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;In the reverse proxy mode, the performance of Nginx and Caddy are basically the same, and both are higher than IIS Out of Process.&lt;/p&gt;
&lt;p&gt;Of course, a single output string test does not represent all the performance of ASP.NET Core 5.0 and each server. In actual projects, there are many factors that affect performance. The design of this experiment does not cover all scenarios, and there must be some flaws. Welcome to leave a message to point out.&lt;/p&gt;
&lt;h3&gt;Addition&lt;/h3&gt;
&lt;p&gt;A friend pointed out that &lt;code&gt;DateTime.UtcNow&lt;/code&gt; may have performance problem in .NET 5, thus I replaced it with &lt;code&gt;Activity.Current?.Id ?? HttpContext.TraceIdentifier&lt;/code&gt;, I get a slight better throughput on each ever, and testing result are almost the same.&lt;/p&gt;
&lt;p&gt;By increasing connection number from 2 to 125, kestrel on each platform can have much higer throughput.&lt;/p&gt;</content></entry><entry><id>08523357-1d24-4f23-8480-d5a8251c1179</id><title>How to Enable Application Request Routing on Azure App Service</title><updated>2020-11-02T07:05:56Z</updated><published>2020-11-02T07:05:56Z</published><link href="https://edi.wang/post/2020/11/2/how-to-enable-application-request-routing-on-azure-app-service" hreflang="en-us" /><author><name>Edi Wang</name><email>Edi.Wang@outlook.com</email></author><category term="Server and Tools" /><category term="Microsoft Azure" /><content>&lt;p&gt;We typically use &lt;a href="https://docs.microsoft.com/en-us/iis/extensions/planning-for-arr/using-the-application-request-routing-module??WT.mc_id=AZ-MVP-5002809" target="_blank" rel="noopener"&gt;Application Request Routing (ARR) module&lt;/a&gt; to host a reverse proxy on IIS. Azure App Service is also using IIS as it's gateway, which should work for reverse proxy, but it won't work by default. Let's see how can we enable ARR on Azure App Service.&lt;/p&gt;
&lt;h2&gt;The Experiment&lt;/h2&gt;
&lt;hr /&gt;
&lt;p&gt;I have &lt;a href="https://dropdatabase.run/"&gt;https://dropdatabase.run/&lt;/a&gt; domain, and I want to reverse proxy &lt;a href="https://996.icu/"&gt;https://996.icu/&lt;/a&gt; under &lt;a href="https://dropdatabase.run/996"&gt;https://dropdatabase.run/996&lt;/a&gt;. So I created a web.config file under &lt;strong&gt;wwwroot&lt;/strong&gt; folder.&lt;/p&gt;
&lt;pre class="language-markup"&gt;&lt;code&gt;&amp;lt;?xml version="1.0" encoding="UTF-8"?&amp;gt;
&amp;lt;configuration&amp;gt;
  &amp;lt;system.webServer&amp;gt;
    &amp;lt;rewrite&amp;gt;
      &amp;lt;rules&amp;gt;
        &amp;lt;rule name="996" stopProcessing="false"&amp;gt;
          &amp;lt;match url="996(.*)" /&amp;gt;
          &amp;lt;action type="Rewrite" url="https://996.icu/{R:1}" appendQueryString="true" logRewrittenUrl="false" /&amp;gt;
        &amp;lt;/rule&amp;gt;
		&amp;lt;rule name="996js" stopProcessing="false"&amp;gt;
          &amp;lt;match url="js/(.*)" /&amp;gt;
          &amp;lt;action type="Rewrite" url="https://996.icu/js/{R:1}" appendQueryString="true" logRewrittenUrl="false" /&amp;gt;
        &amp;lt;/rule&amp;gt;
      &amp;lt;/rules&amp;gt;
    &amp;lt;/rewrite&amp;gt;
    &amp;lt;httpProtocol&amp;gt;
     &amp;lt;customHeaders&amp;gt;
        &amp;lt;add name="strict-transport-security" value="max-age=15552001; includeSubDomains; preload" /&amp;gt;
     &amp;lt;/customHeaders&amp;gt;
    &amp;lt;/httpProtocol&amp;gt;
  &amp;lt;/system.webServer&amp;gt;
&amp;lt;/configuration&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It works on my local machine, but on Azure App Service, it went 404.&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-9d6259b3-0c6b-4a6a-9b2b-545e94bc2556.png" alt="" loading="lazy" /&gt;&lt;/p&gt;
&lt;p&gt;The web.config is correct, but it won't work because Azure App Service doesn't by default enable the ARR module although it is preinstalled.&lt;/p&gt;
&lt;h2&gt;The Solution&lt;/h2&gt;
&lt;hr /&gt;
&lt;p&gt;Let's do some magic that you won't easily find on Microsoft document. To enable ARR on Azure App Service, you need to create an xdt file and upload to your site directory.&lt;/p&gt;
&lt;p&gt;Go to&lt;strong&gt; Advanced Tools &lt;/strong&gt;on your website's management blade.&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-8adf2a9b-ec2e-4c4e-965d-03abf6ffa194.png" alt="" width="665" height="370" loading="lazy" /&gt;&lt;/p&gt;
&lt;p&gt;Go to &lt;strong&gt;Debug console, CMD or PowerShell&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-87fb6167-4a06-458a-8392-4728ad1ab2e0.png" alt="" width="468" height="143" loading="lazy" /&gt;&lt;/p&gt;
&lt;p&gt;Enter &lt;strong&gt;site &lt;/strong&gt;directory&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-933fad70-6f13-48a7-a7e1-ddb4a46d68cb.png" alt="" width="657" height="396" loading="lazy" /&gt;&lt;/p&gt;
&lt;p&gt;Click &lt;strong&gt;+ New file&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-ef653f16-354d-403d-b3e9-0406f78d228a.png" alt="" width="534" height="307" loading="lazy" /&gt;&lt;/p&gt;
&lt;p&gt;Enter the magic filename &lt;strong&gt;applicationHost.xdt&lt;/strong&gt; and click the pen icon for editing it's content&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-557da156-ffe0-4601-bd14-577bfce234b1.png" alt="" width="463" height="235" loading="lazy" /&gt;&lt;/p&gt;
&lt;p&gt;Copy the following content and save the file&lt;/p&gt;
&lt;pre class="language-markup"&gt;&lt;code&gt;&amp;lt;?xml version="1.0" encoding="UTF-8"?&amp;gt;
&amp;lt;configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform"&amp;gt;
  &amp;lt;system.webServer&amp;gt;
    &amp;lt;proxy xdt:Transform="InsertIfMissing" enabled="true" preserveHostHeader="false" reverseRewriteHostInResponseHeaders="false" /&amp;gt;
  &amp;lt;/system.webServer&amp;gt;
&amp;lt;/configuration&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Go back to Azure App Service and restart your website&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-fa687a5d-4a2c-4410-9539-79fa06cb72b3.png" alt="" width="863" height="430" loading="lazy" /&gt;&lt;/p&gt;
&lt;p&gt;The ARR will be enabled after the website restarts, and our reverse proxy works as expected now&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-5637cf44-ec99-47e4-a48f-a1725195d4af.png" alt="" width="1609" height="928" loading="lazy" /&gt;&lt;/p&gt;</content></entry><entry><id>0a182d9a-42bc-42ab-8683-35e0884776ae</id><title>Run ASP.NET Core Websites with Windows Server Core on Azure</title><updated>2019-08-07T05:24:02Z</updated><published>2019-08-07T05:24:02Z</published><link href="https://edi.wang/post/2019/8/7/run-aspnet-core-websites-with-windows-server-core-on-azure" hreflang="en-us" /><author><name>Edi Wang</name><email>Edi.Wang@outlook.com</email></author><category term="Server and Tools" /><category term="Microsoft Azure" /><content>&lt;p&gt;Microsoft Azure provides &lt;a href="https://azure.microsoft.com/en-us/services/app-service/?WT.mc_id=AZ-MVP-5002809" target="_blank" rel="noopener"&gt;App Service&lt;/a&gt; that is fully managed to enable you to run websites. However, in some cases, you may still need an IaaS solution like a VM to host your website. This article will show you how to run an ASP.NET Core 2.2 web application on a Windows Server 2019 Datacenter Server Core virtual machine hosted in Azure.&lt;/p&gt;
&lt;h2&gt;About Windows Server Core&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;Windows was designed as an operating system with a GUI, even on its server SKUs, until Windows Server 2008, Microsoft ships a "Server Core" version of Windows that strip off most of the GUI components like Windows Explorer, Taskbar, Control Panels. Comparing to the full desktop experience, Server Core has certain advantages, as Microsoft explains:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;"The Server Core installation eliminates any services and other features that are not essential for the support of certain commonly used server roles. For example, a Hyper-V server doesn't need a graphical user interface (GUI), because you can manage virtually all aspects of Hyper-V either from the command line using Windows PowerShell or remotely using the Hyper-V Manager."&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;You can read more about Windows Server Core on &lt;a href="https://docs.microsoft.com/en-us/windows-server/administration/server-core/what-is-server-core?WT.mc_id=AZ-MVP-5002809" target="_blank" rel="noopener"&gt;Microsoft Docs&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;But you should keep in mind that although Windows Server Core is mostly managed via command lines, it isn't like Linux, there are still GUI components left like Registry Editor which we are going to use in this article. If you want a full command-line version of Windows Server, please check out &lt;a href="https://docs.microsoft.com/en-us/windows-server/get-started/getting-started-with-nano-server?WT.mc_id=AZ-MVP-5002809" target="_blank" rel="noopener"&gt;Nano Server&lt;/a&gt; on Container.&lt;/p&gt;
&lt;h2&gt;Create Windows Server Core VM on Azure&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;The Server Core images aren't in the drop-down list by default. You need to click "&lt;strong&gt;Browse all public and private images&lt;/strong&gt;" to find the "&lt;strong&gt;Windows Server 2019 Datacenter Server Core&lt;/strong&gt;" image and create your server just like any other Azure VM.&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-9879183b-047e-4a16-b9a6-a3a55fa12305.png" border="0" alt="" loading="lazy"&gt;&lt;/p&gt;
&lt;p&gt;Since we are running an ASP.NET Core website and need to configure it using the RDP, it is recommended that we open the 80,443,3389 port.&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-5ff491d9-8daa-4526-9ae9-f5254a9baf9f.png" border="0" alt="" width="755" height="252" loading="lazy"&gt;&lt;/p&gt;
&lt;p&gt;Because of the testing purpose, I chose not to configure the firewall and expose all ports to the public network (and also ignore the previous port settings). In a real-world environment, do not do this.&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-25067797-c0e4-4597-a0cf-97037da1ec32.png" border="0" alt="" width="765" height="443" loading="lazy"&gt;&lt;/p&gt;
&lt;p&gt;When the server deployment is complete, click on the "&lt;strong&gt;Configure&lt;/strong&gt;" in &lt;strong&gt;DNS name&lt;/strong&gt; to configure a domain name. This is because the default IP address for VMs on Azure is dynamic, so it's best to access it through a domain name to make sure you can find your server at any time.&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-64f01104-eb0d-4566-b9a6-a0188862f5f9.png" border="0" alt="" width="1191" height="538" loading="lazy"&gt;&lt;/p&gt;
&lt;p&gt;Assign the DNS name label.&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-e5e3c7f2-0432-4911-8484-07cf554a92f3.png" border="0" alt="" width="611" height="486" loading="lazy"&gt;&lt;/p&gt;
&lt;p&gt;When the configuration is complete, refresh the VM home page to view and copy the domain name used to access the virtual machine.&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-0f5bd5a1-d29b-4764-bdeb-ed335a42ada0.png" border="0" alt="" width="539" height="140" loading="lazy"&gt;&lt;/p&gt;
&lt;h2&gt;Sign In to Server Core VM&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;Click "&lt;strong&gt;Connect&lt;/strong&gt;"&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-a836b3ca-fe27-4bdb-84dd-f6e371041d92.png" border="0" alt="" width="542" height="250" loading="lazy"&gt;&lt;/p&gt;
&lt;p&gt;Choose "&lt;strong&gt;Download RDP File&lt;/strong&gt;"&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-b15c1883-7ae8-442b-8b9c-15d08d8052f3.png" border="0" alt="" width="439" height="463" loading="lazy"&gt;&lt;/p&gt;
&lt;p&gt;Open the RDP file and you can sign in to the Server Core VM with the account password you set when you created the server.&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-8ddcd523-4763-4acb-9d74-ad51210551e6.png" border="0" alt="" width="545" height="226" loading="lazy"&gt;&lt;/p&gt;
&lt;p&gt;At this point, you'll find that Server Core is not completely removed it's GUI, but rather minimizes the presence of the GUI. The cmd window is still rendered by Windows. The registry editor we're going to use later is also in GUI.&lt;/p&gt;
&lt;h2&gt;Setup IIS&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;First, I borrow some code I wrote before (&lt;a href="https://github.com/EdiWang/EnvSetup" target="_blank" rel="noopener"&gt;https://github.com/EdiWang/EnvSetup&lt;/a&gt;) to install IIS (except ASP.NET, because we're using .NET Core, it has nothing to do with classic ASP.NET).&lt;/p&gt;
&lt;p&gt;Enter &lt;strong&gt;powershell&amp;nbsp;&lt;/strong&gt;to switch to Windows PowerShell under cmd, and execute these commands in order:&lt;/p&gt;
&lt;pre class="language-powershell"&gt;&lt;code&gt;Enable-WindowsOptionalFeature-Online -FeatureName IIS-DefaultDocument -All
Enable-WindowsOptionalFeature-Online -FeatureName IIS-HttpCompressionDynamic -All
Enable-WindowsOptionalFeature-Online -FeatureName IIS-HttpCompressionStatic -All
Enable-WindowsOptionalFeature-Online -FeatureName IIS-WebSockets -All
Enable-WindowsOptionalFeature-Online -FeatureName IIS-ApplicationInit -All
Enable-WindowsOptionalFeature-Online -FeatureName IIS-ServerSideIncludes
Enable-WindowsOptionalFeature-Online -FeatureName IIS-BasicAuthentication
Enable-WindowsOptionalFeature-Online -FeatureName IIS-WindowsAuthentication&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This step can also be done with the &lt;code&gt;Install-Windows Feature Web-Server&lt;/code&gt; command, but I haven't tried it, and you can try it yourself.&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-62dc1a36-590e-4d07-beaa-f54924fbb7ca.png" border="0" alt="" width="801" height="536" loading="lazy"&gt;&lt;/p&gt;
&lt;p&gt;Install Management Service.&lt;/p&gt;
&lt;pre class="language-powershell"&gt;&lt;code&gt;Install-WindowsFeatureWeb-Mgmt-Service&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src="/image/img-2e2395f2-6d3a-448b-a6b7-b95206a9095f.png" border="0" alt="" width="591" height="228" loading="lazy"&gt;&lt;/p&gt;
&lt;p&gt;Run &lt;strong&gt;regedit&amp;nbsp;&lt;/strong&gt;and navigate to:&lt;/p&gt;
&lt;pre class="language-markup"&gt;&lt;code&gt;HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\WebManagement\Server&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Change &lt;strong&gt;EnableRemoteManagement&amp;nbsp;&lt;/strong&gt;to &lt;strong&gt;1&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-10b1a242-9043-4fff-a94f-072979ea92d7.png" border="0" alt="" width="748" height="456" loading="lazy"&gt;&lt;/p&gt;
&lt;p&gt;Enter &lt;strong&gt;exit&lt;/strong&gt; to go back to cmd, we need to configure &lt;strong&gt;wmsvc&lt;/strong&gt; to auto start, and then start it.&lt;/p&gt;
&lt;pre class="language-markup"&gt;&lt;code&gt;scconfig wmsvc start=auto
netstart wmsvc&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src="/image/img-fc69ef77-1346-432b-bb00-f4267b0be5ae.png" border="0" alt="" width="688" height="358" loading="lazy"&gt;&lt;/p&gt;
&lt;p&gt;And to allow Windows Firewall (not Azure NSG) to pass through this service.&lt;/p&gt;
&lt;pre class="language-markup"&gt;&lt;code&gt;netsh advfirewall firewall add rule name=&amp;rdquo;IIS Remote Management&amp;rdquo; dir=in action=allow service=WMSVC&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you have enabled Azure NSG, please open port &lt;strong&gt;8172&lt;/strong&gt;.&lt;/p&gt;
&lt;h2&gt;Install ASP.NET Core Server Environment&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;Although we can download the .NET Core runtime and install it manually in PowerShell, it's not convenient. There's actually a command-line package management tool on Windows that's called &lt;strong&gt;chocolatey&lt;/strong&gt;, and we can install a lot of software fully automatically from the command line.&lt;/p&gt;
&lt;p&gt;Enter PowerShell again, and install &lt;strong&gt;choco&lt;/strong&gt;&lt;/p&gt;
&lt;pre class="language-powershell"&gt;&lt;code&gt;Set-ExecutionPolicyBypass -Scope Process -Force; iex ((New-ObjectSystem.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When you're done, type &lt;strong&gt;choco&lt;/strong&gt; to check if it is installed.&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-799bc41d-7940-4817-91cd-83fa02a50066.png" border="0" alt="" loading="lazy"&gt;&lt;/p&gt;
&lt;p&gt;Execute the following command to installation ASP.NET Core server environment automatically.&lt;/p&gt;
&lt;pre class="language-markup"&gt;&lt;code&gt;chocoinstall dotnetcore-windowshosting -y&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It will install both x86 and x64 runtimes, and IIS's ANCM module&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-d78c83fb-e683-47b9-87a4-cabd3758164d.png" border="0" alt="" width="590" height="364" loading="lazy"&gt;&lt;/p&gt;
&lt;h5&gt;Install Web Deploy&lt;/h5&gt;
&lt;hr&gt;
&lt;p&gt;Use choco to install Web Deploy, which is then used to remotely publish ASP.NET Core sites.&lt;/p&gt;
&lt;pre class="language-markup"&gt;&lt;code&gt;chocoinstall webdeploy -y&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src="/image/img-4a082bf0-9cbc-4a0a-8f3a-e483e7b9b2df.png" border="0" alt="" width="599" height="355" loading="lazy"&gt;&lt;/p&gt;
&lt;p&gt;Restart &lt;strong&gt;w3svc&lt;/strong&gt; service&lt;/p&gt;
&lt;pre class="language-markup"&gt;&lt;code&gt;net stop w3svc
net start w3svc&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src="/image/img-656c431d-3b90-41cc-821c-b5e01970082d.png" border="0" alt="" width="580" height="182" loading="lazy"&gt;&lt;/p&gt;
&lt;h2&gt;Manage IIS&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;We are now able to connect remotely to this server in the IIS Manager for website publishing. This can be done on any machine with an IIS 7+ manager, such as our own Windows 10 client.&lt;/p&gt;
&lt;p&gt;You need to download an add-in for this. &lt;a href="https://www.microsoft.com/en-us/download/details.aspx?id=41177" target="_blank" rel="noopener"&gt;&lt;strong&gt;IISManager for Remote Administration 1.2&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Click &lt;strong&gt;File-&amp;gt; Connect to a Server&amp;nbsp;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-cae1fa0b-35bc-4cd2-b746-d8e9bcd7a65a.png" border="0" alt="" width="301" height="250" loading="lazy"&gt;&lt;/p&gt;
&lt;p&gt;Enter our server address, which is the DNS name that was assigned by Azure.&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-fb218085-7f08-41fd-93b9-0c5abda525c5.png" border="0" alt="" width="385" height="214" loading="lazy"&gt;&lt;/p&gt;
&lt;p&gt;Ignore certificate warning, click &lt;strong&gt;Connect&amp;nbsp;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-6efaa69e-1f9b-47ae-bbf7-6a8788092163.png" border="0" alt="" width="676" height="514" loading="lazy"&gt;&lt;/p&gt;
&lt;p&gt;Install &lt;strong&gt;all&lt;/strong&gt; the components in the popup dialog.&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-a8f586f4-a3e4-4e44-aa22-2cf3224de488.png" border="0" alt="" width="575" height="396" loading="lazy"&gt;&lt;/p&gt;
&lt;p&gt;Now you should be able to see and manage websites on the remote server. First, we need to check if &lt;strong&gt;ANCM&lt;/strong&gt; is installed on the server.&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-1e3ac9fe-2eca-4ec2-9670-c77ed93f5b43.png" border="0" alt="" width="780" height="399" loading="lazy"&gt;&lt;/p&gt;
&lt;p&gt;Check if &lt;strong&gt;AspNetCoreModule&amp;nbsp;&lt;/strong&gt;and &lt;strong&gt;AspNetCoreModuleV2&amp;nbsp;&lt;/strong&gt;exists. These are the IIS modules to run ASP.NET Core websites.&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-3638bfbd-75f0-4b6c-97ca-6164cb1ccfcf.png" border="0" alt="" width="449" height="228" loading="lazy"&gt;&lt;/p&gt;
&lt;p&gt;Of course, you can also use the &lt;strong&gt;dotnet-info&lt;/strong&gt; command on the server to check the runtime version.&lt;/p&gt;
&lt;p&gt;The most convenient way to refresh the environment variable is to restart cmd. However, if we exit cmd on the server, because there is no complete desktop, do not know where to start cmd. The trick is, press CTRL+SHIFT+ESC call out the task manager, and then run the &lt;strong&gt;cmd&lt;/strong&gt; command.&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-bb8466e5-5446-4432-ace9-a06c01f24e65.png" border="0" alt="" width="583" height="260" loading="lazy"&gt;&lt;/p&gt;
&lt;p&gt;Now you can check the .NET Core version.&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-0ac7b7a8-f5fb-48f5-889d-edbc0795e5a1.png" border="0" alt="" width="849" height="551" loading="lazy"&gt;&lt;/p&gt;
&lt;p&gt;Now, go back to the IIS manager on the local PC. Assign the default site with https support in IIS because .NET Core now enables https by default (although we don't have a certificate).&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-dae0ba1e-4362-49b8-ba7c-3c4a0f69df8f.png" border="0" alt="" width="1018" height="729" loading="lazy"&gt;&lt;/p&gt;
&lt;p&gt;Then try to access the URL for both &lt;strong&gt;http&lt;/strong&gt; and &lt;strong&gt;https&lt;/strong&gt; protocol, which is your VM DNS name, ignore the certificate warning, and should be able to open the default IIS welcome page.&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-74900df0-8b49-4880-9b53-a0a35a6b6c8f.png" border="0" alt="" width="792" height="569" loading="lazy"&gt;&lt;/p&gt;
&lt;h2&gt;Publish ASP.NET Core Website&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;While the CI/CD tool, such as Azure DevOps, is fully capable of using Web Deploy to publish websites, it can save us a lot of time, but what's the fun in that? I'll show you how to do it manually.&lt;/p&gt;
&lt;p&gt;Compile an ASP.NET Core site and publish it to the local file system. Then put the published file into a zip package.&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-5622ecaa-0dec-4b97-95fc-d7dbc421db9a.png" border="0" alt="" loading="lazy"&gt;&lt;/p&gt;
&lt;p&gt;Then go back to IIS manager, right-click on the website, select &lt;strong&gt;Deploy / Import Application&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-46997a4f-ea65-4390-8777-2c85b7c75cce.png" border="0" alt="" width="565" height="372" loading="lazy"&gt;&lt;/p&gt;
&lt;p&gt;Select the zip file you just packaged&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-82b0d95d-3a1c-4f3e-8fdc-bca59efb9fc7.png" border="0" alt="" width="694" height="539" loading="lazy"&gt;&lt;/p&gt;
&lt;p&gt;Select all the contents.&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-0eaccf82-f5be-42a6-aa32-a613115e309d.png" border="0" alt="" width="680" height="522" loading="lazy"&gt;&lt;/p&gt;
&lt;p&gt;Enter the site name (must be the same name as the site displayed in the IIS Manager)&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-2d06b41e-6745-45ed-a323-6af6c2a41cb1.png" border="0" alt="" width="387" height="284" loading="lazy"&gt;&lt;/p&gt;
&lt;p&gt;Finish the import.&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-2959a38d-1532-4caa-a29f-0677d63908d0.png" border="0" alt="" width="365" height="283" loading="lazy"&gt;&lt;/p&gt;
&lt;p&gt;Now you can see the website's content in IIS Manager.&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-42c6602d-6075-474c-96d0-78820c3b693b.png" border="0" alt="" width="656" height="329" loading="lazy"&gt;&lt;/p&gt;
&lt;p&gt;Try to access the VM URL now, you can see the ASP.NET Core website running.&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-8c022d2e-1c17-4f86-a29c-3aed626eae70.png" border="0" alt="" width="974" height="476" loading="lazy"&gt;&lt;/p&gt;</content></entry><entry><id>bb08883f-915c-4c6e-8ee1-1e4a0d6fa73b</id><title>Run Classic ASP on Windows 10 and Azure App Service</title><updated>2019-07-17T12:47:55Z</updated><published>2019-07-17T12:47:55Z</published><link href="https://edi.wang/post/2019/7/17/run-classic-asp-on-windows-10-and-azure-app-service" hreflang="en-us" /><author><name>Edi Wang</name><email>Edi.Wang@outlook.com</email></author><category term="Server and Tools" /><category term="Microsoft Azure" /><content>&lt;p&gt;ASP is an old technology of Microsoft that even before .NET was born. I have used ASP 3.0 to create my first personal blog in 2003. Nowadays, it is hard to find ASP web applications still active on the internet. But we can still bring the 1996's classic ASP back to life on today's Windows 10 and even in Azure.&lt;/p&gt;
&lt;h2&gt;Some history&lt;/h2&gt;
&lt;hr /&gt;
&lt;p&gt;ASP and its successor, ASP.NET are completely different. ASP uses the VBScript/JScript (not JavaScript) script engine to execute on the server side and returns the generated HTML. ASP.NET on the other hand, whether it is an ancient WebForm or a modern MVC, needs to be compiled and executed by the .NET runtime. ASP's development tools don't have to be Visual Studio, any text editor will do the job. So we don't need to install special development tools for writing ASP code today. VSCode, and even notepad will do the job.&lt;/p&gt;
&lt;h2&gt;Run ASP on Windows 10&lt;/h2&gt;
&lt;hr /&gt;
&lt;p&gt;Because ASP is way too old, IIS in Windows 10 does not enable ASP support by default, so we need to find it manually and turn it on.&lt;/p&gt;
&lt;p&gt;Run "&lt;strong&gt;appwiz.cpl&lt;/strong&gt;", and select "&lt;strong&gt;ASP&lt;/strong&gt;" under &lt;strong&gt;Windows features / Internet Information Services&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-39b269e3-7bb4-4ff0-960e-1c10e441fc74.png" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;Go to &lt;strong&gt;Application Pools&lt;/strong&gt; and create a new application pool just for ASP environment named &lt;strong&gt;Classic ASP&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-7b6a6dec-e33a-4707-9451-fcbe6cb704dd.png" alt="" width="528" height="308" /&gt;&lt;/p&gt;
&lt;p&gt;Set &lt;strong&gt;.NET CLR Version&lt;/strong&gt; to&lt;strong&gt; No Managed Code&lt;/strong&gt;. This is because ASP is not executed by .NET, there is no need for CLR to exist.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Set &lt;strong&gt;Managed pipleline mode&amp;nbsp;&lt;/strong&gt;to &lt;strong&gt;Classic&lt;/strong&gt;. (Optional, but it's more ASPish way to make IIS more "decouple" with ASP.NET pipeline).&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-e3d61a32-8f97-4232-9276-398aaf42bf8d.png" alt="" width="302" height="279" /&gt;&lt;/p&gt;
&lt;p&gt;Enter &lt;strong&gt;Advanced Settings&amp;nbsp;&lt;/strong&gt;of this application pool&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-351d8bed-3a56-46d1-ba79-cf5b3c857af4.png" alt="" width="356" height="322" /&gt;&lt;/p&gt;
&lt;p&gt;Set &lt;strong&gt;Enable 32-Bit Applications&amp;nbsp;&lt;/strong&gt;to &lt;strong&gt;True&lt;/strong&gt;. Because at the age of ASP, computers aren't 64 bit architecture, the VBScript engine can only run at 32 bit.&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-026bf356-ebb6-49c8-8127-1f74fe480f90.png" alt="" width="447" height="299" /&gt;&lt;/p&gt;
&lt;p&gt;Now, create an empty folder in your local drive, set it as an Application or a Website under IIS. E.g.: &lt;strong&gt;helloasp&lt;/strong&gt;&amp;nbsp;pointing to &lt;strong&gt;D:\Workspace\OldSchool&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;And set it's &lt;strong&gt;Application Pool&lt;/strong&gt; to use &lt;strong&gt;Classic ASP&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-c4ec8d55-dac1-4519-8b8f-d8aaf82a5511.png" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-e3694f1c-d5a7-4b31-8477-ca6ed32594b8.png" alt="" width="518" height="385" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Prepare an ASP test page&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Create a new file named "&lt;strong&gt;default.asp&lt;/strong&gt;" under the website's root directory.&lt;/p&gt;
&lt;pre class="language-markup"&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang="en"&amp;gt;
&amp;lt;head&amp;gt;
&amp;lt;meta charset="UTF-8"&amp;gt;
&amp;lt;meta name="viewport" content="width=device-width, initial-scale=1.0"&amp;gt;
&amp;lt;meta http-equiv="X-UA-Compatible" content="ie=edge"&amp;gt;
&amp;lt;title&amp;gt;Hello ASP&amp;lt;/title&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
    &amp;lt;% Response.Write("I am back!") %&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this code, &lt;code&gt;&amp;lt;% Response.Write("I am back!") %&amp;gt;&lt;/code&gt; is an ASP code to output content.&lt;/p&gt;
&lt;p&gt;It's interesting that ASP would have never dreamed that one day it will be written in Visual Studio Code:&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-719b28af-de13-4296-8d10-645721a7a05f.png" alt="" width="1013" height="595" /&gt;&lt;/p&gt;
&lt;p&gt;Back to IIS and browse our website, you will see this ASP page running:&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-532c24fd-28e2-492e-8f0b-dd0414d885c1.png" alt="" width="373" height="214" /&gt;&lt;/p&gt;
&lt;p&gt;If doesn't, check if "default.asp" is in your Default Document settings in IIS.&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-4f7c30ae-a69c-4945-a8cb-ea72249b85fb.png" alt="" /&gt;&lt;/p&gt;
&lt;h2&gt;Run ASP in Azure App Service&lt;/h2&gt;
&lt;hr /&gt;
&lt;p&gt;Azure App Service actually supports ASP although it's not mentioned in the official materials. But one premise is that in order to run ASP, your App Service Plan environment must be Windows.&lt;/p&gt;
&lt;p&gt;Enter &lt;strong&gt;Configuration&amp;nbsp;&lt;/strong&gt;in App Service settings.&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-042f658b-f59b-4d11-8b1c-f50047afc521.png" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;Under &lt;strong&gt;General settings&lt;/strong&gt;, set &lt;strong&gt;Platform&amp;nbsp;&lt;/strong&gt;to &lt;strong&gt;32 Bit&lt;/strong&gt;, and change &lt;strong&gt;Managed pipeline version&amp;nbsp;&lt;/strong&gt;to &lt;strong&gt;Classic&lt;/strong&gt;. Although ASP is not in the Stack list, but keeping it as .NET 4.7 won't affect anything.&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-931698f8-63d8-4535-82da-652505e04cbf.png" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;If you like, you can change &lt;strong&gt;HTTP Version&amp;nbsp;&lt;/strong&gt;to use 2.0. ASP would never have seen itself running under HTTP2 on a cloud PaaS system, interesting!&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-0bf925d8-6d2b-4936-b5bb-728ac0893479.png" alt="" /&gt;&lt;/p&gt;</content></entry><entry><id>58f062d6-ee90-4411-8e2f-4768c8d37f8f</id><title>Path Caveat with ASP.NET Core 2.2 IIS Hosting</title><updated>2018-12-27T02:47:40Z</updated><published>2018-12-27T02:47:40Z</published><link href="https://edi.wang/post/2018/12/27/path-caveat-with-aspnet-core-22-iis-hosting" hreflang="en-us" /><author><name>Edi Wang</name><email>Edi.Wang@outlook.com</email></author><category term=".NET" /><category term="Server and Tools" /><content>&lt;p&gt;ASP.NET Core 2.2 has been in place for some time, with a new feature that can use the new &lt;strong&gt;AspNetCoreModuleV2&lt;/strong&gt; and deploy with &lt;strong&gt;InProcess&lt;/strong&gt; mode on IIS to dramatically improve performance. These days Azure App Service finally completed the deployment of this new version of the module, I configured my blog to the new module, and it exploded in production. Let's see why and how to solve it.&lt;/p&gt;
&lt;p&gt;If you don't know what &lt;strong&gt;InProcess&lt;/strong&gt; mode is, simply speaking, the original ASP.NET Core can actually run on IIS, but it was done by an IIS module called &lt;strong&gt;AspNetCoreModule&lt;/strong&gt;, use &lt;strong&gt;dotnet.exe&lt;/strong&gt; to start &lt;strong&gt;Kestrel&lt;/strong&gt;, so the process name was actually dotnet.exe.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;InProcess&lt;/strong&gt; model has been added to ASP.NET Core 2.2 to run your app in IIS's own w3wp process. It is said to have 400% performance improvement &lt;a href="https://blogs.msdn.microsoft.com/webdev/2018/12/04/asp-net-core-2-2-available-today/" target="_blank" rel="noopener"&gt;in this MSDN blog post&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://docs.microsoft.com/en-us/aspnet/core/fundamentals/servers/_static/ancm-outofprocess.png?view=aspnetcore-2.2" border="0" alt="ASP.NET Core Module" loading="lazy"&gt;&lt;/p&gt;
&lt;p&gt;To learn more about these two hosting models, you can check &lt;a href="https://docs.microsoft.com/en-us/aspnet/core/fundamentals/servers/index?view=aspnetcore-2.2&amp;amp;tabs=windows#in-process-hosting-model"&gt;https://docs.microsoft.com/en-us/aspnet/core/fundamentals/servers/index?view=aspnetcore-2.2&amp;amp;tabs=windows#in-process-hosting-model&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2&gt;Production Blow Up&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;Based on the original understanding of ASP.NET Core, I generally do not use IIS to test my App, I only use Kestrel in the development environment, and then use Azure DevOps to deploy the App to Azure App service, As a result of today's upgrade to InProcess mode, the production environment exploded, while the Kestrel of the development machine is good.&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-68319c30-caad-4ccf-b640-67726499843f.png" border="0" alt="" width="783" height="148" loading="lazy"&gt;&lt;/p&gt;
&lt;p&gt;Startup failed, my entire blog site cannot be accessed, holy high!&lt;/p&gt;
&lt;h2&gt;Root Cause Analysis&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;Fortunately, the Microsoft Cloud "Azure" offers the world's unique &lt;strong&gt;Kudu&lt;/strong&gt; tools that make it easy to see logs, and logs show:&lt;/p&gt;
&lt;pre class="language-markup"&gt;&lt;code&gt;[2018-12-26 12:06:26.5616][RD00155DB8C92A]
[Fatal][Microsoft.AspNetCore.Hosting.Internal.WebHost] 
Application startup exception System.IO.FileNotFoundException: Could not find file 'D:\Windows\system32\urlrewrite.xml'.

File name: 'D:\Windows\system32\urlrewrite.xml'&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;How did this happen? My code access clearly the root directory of the application, why it ran to the system directory? One more look at the startup log:&lt;/p&gt;
&lt;pre class="language-markup"&gt;&lt;code&gt;[2018-12-26 12:06:23.7946][RD00155DB8C92A]
[Info][Moonglade.Web.Program] Moonglade is starting, hail Microsoft! 
--------------------------------------------------------
 Version: 10.0.6934.1000 
 Directory: D:\Windows\system32 
 x64Process: True 
 OSVersion: Microsoft Windows 10.0.14393  
 AppDomain: Moonglade.Web 
 UserName: moonglade 
-------------------------------------------------------- &lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The problem code is:&lt;/p&gt;
&lt;pre class="language-csharp"&gt;&lt;code&gt;using (var sr = File.OpenText("urlrewrite.xml"))
{
    // ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;span style="color: #ff0000;"&gt;It turns out that when it's running under Kestrel, &lt;strong&gt;Environment.CurrentDirectory&lt;/strong&gt; points to the application root directory, while running in IIS's &lt;strong&gt;InProcess&lt;/strong&gt; mode, it points to the system directory, which eventually causes the app to explode with either indirect or direct use of &lt;strong&gt;Environment.CurrentDirectory&lt;/strong&gt; code.&lt;/span&gt;&lt;/p&gt;
&lt;h2&gt;Fixing the Problem&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;We can take advantage of the &lt;strong&gt;ContentRootPath&lt;/strong&gt; property provided in the &lt;strong&gt;IHostingEnvironment&lt;/strong&gt; interface to get the absolute path to the current app directory, which behaves consistently in Kestrel and IIS. My problem code is exactly where you can access &lt;strong&gt;IHostingEnvironment&lt;/strong&gt;, so make the following modifications:&lt;/p&gt;
&lt;pre class="language-csharp"&gt;&lt;code&gt;public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    string baseDir = env.ContentRootPath;
    using (var sr = File.OpenText(Path.Combine(baseDir, "urlrewrite.xml")))
    {
        //...
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For places that don't have direct access to &lt;strong&gt;IHostingEnvironment&lt;/strong&gt; and you don't want to do Dependency Injections, you can code like this:&lt;/p&gt;
&lt;pre class="language-csharp"&gt;&lt;code&gt;public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    string baseDir = env.ContentRootPath;
    //...
    AppDomain.CurrentDomain.SetData(Constants.AppBaseDirectory, baseDir);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And call &lt;strong&gt;GetData()&lt;/strong&gt; in places where you are using this path.&lt;/p&gt;
&lt;pre class="language-csharp"&gt;&lt;code&gt;var configSource = $@"{AppDomain.CurrentDomain.GetData(Constants.AppBaseDirectory)}\mailConfiguration.xml";&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I deployed my blog system again, the problem is fixed!&lt;/p&gt;
&lt;h2&gt;Other Things to Remind&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;If you are using VS to debug your application under IIS, like this:&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-a3f58732-937f-4349-891b-3526932d35e2.png" border="0" alt="" width="443" height="232" loading="lazy"&gt;&lt;/p&gt;
&lt;p&gt;This will generate a debug version of web.config under your application root, and will be used in your CI/CD pipeline all the way up to production! &lt;span style="color: #ff0000;"&gt;This will make your production application run as &lt;strong&gt;debug&lt;/strong&gt; while your Build is set to &lt;strong&gt;release&lt;/strong&gt; mode&lt;/span&gt;. Please do exclude this web.config file manually.&lt;/p&gt;
&lt;p&gt;Finally, with ASP.NET Core 2.2, we can no longer take for granted that the development of ASP.NET with/out IIS does not matter. Some things in IIS and Kestrel are behavior inconsistently, so it is recommended that if the production environment is using IIS, be sure to test it with the same configuration on your local IIS before deploying to production.&lt;/p&gt;</content></entry><entry><id>4e7ab596-a3e2-409b-995d-c9ed62129333</id><title>Prevent Image Hotlinking in ASP.NET/Core Applications</title><updated>2018-09-18T14:25:46Z</updated><published>2018-09-18T14:25:46Z</published><link href="https://edi.wang/post/2018/9/18/prevent-image-hotlink-aspnet-core" hreflang="en-us" /><author><name>Edi Wang</name><email>Edi.Wang@outlook.com</email></author><category term=".NET" /><category term="Server and Tools" /><content>&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;hr /&gt;
&lt;p&gt;As a website developer, we sometimes don't want the images on our own website to be directly referenced and showed on other's website. It can cause a lot network bandwidth for our datacenters in some cases, which means costing money for us to pay for the one who use our images.&lt;/p&gt;
&lt;p&gt;For example, your website is A.com, you have an image on http://a.com/facepalm.jpg and B.com used your image on b.com by putting an &amp;lt;img&amp;gt; tag in their html, it can cause network request going in to your server consuming your resources.&lt;/p&gt;
&lt;p&gt;&lt;img style="display: block; margin-left: auto; margin-right: auto;" src="/image/img-a7d4e97f-6234-41cb-aa31-6c4b85fe683c.png" /&gt;&lt;/p&gt;
&lt;p&gt;A lot of websites prevent image hotlinking. Let me show you how we can do it in ASP.NET or ASP.NET Core application.&lt;/p&gt;
&lt;p&gt;The technique I use is URL Rewrite. As we know, every http request has some headers, one of them is the "HTTP referer". You can check more details on &lt;a href="https://en.wikipedia.org/wiki/HTTP_referer" target="_blank" rel="noopener"&gt;Wikipedia here&lt;/a&gt;. It applies to image files also, if the image is hotlinking from another website. It will have HTTP referer values of another website.&lt;/p&gt;
&lt;p&gt;In the example above, if the user lands on b.com visiting &lt;strong&gt;http://a.com/facepalm.jpg&lt;/strong&gt;,&amp;nbsp;for a.com's web server, the http requst for this particular image will have an http header named "referer" with value of "&lt;strong&gt;http://b.com....&lt;/strong&gt;". This is where we will check and block the request.&lt;/p&gt;
&lt;h2&gt;ASP.NET (.NET Framework) Applications&lt;/h2&gt;
&lt;hr /&gt;
&lt;p&gt;For classic ASP.NET (.NET Framework) application on IIS, you have to install the URL Rewrite extension on IIS&amp;nbsp;&lt;a href="https://www.iis.net/downloads/microsoft/url-rewrite"&gt;https://www.iis.net/downloads/microsoft/url-rewrite&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Create a file named "&lt;strong&gt;UrlRewrite.config&lt;/strong&gt;" under your application root directory with the content below:&lt;/p&gt;
&lt;pre class="language-markup"&gt;&lt;code&gt;&amp;lt;rules&amp;gt;
  &amp;lt;rule name="Prevent Image Hotlinking"&amp;gt;
    &amp;lt;match url=".*\.(gif|jpg|png)$"/&amp;gt;
    &amp;lt;conditions&amp;gt;
      &amp;lt;add input="{HTTP_REFERER}" pattern="^$" negate="true"/&amp;gt;
      &amp;lt;add input="{HTTP_REFERER}" pattern="^http://a.com/.*$" negate="true"/&amp;gt;
    &amp;lt;/conditions&amp;gt;
    &amp;lt;action type="Rewrite" url="/content/images/no_hotlinking.png"/&amp;gt;
  &amp;lt;/rule&amp;gt;
&amp;lt;/rules&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This rule means, for the image file type that ends with .&lt;strong&gt;gif, .jpg, .png&lt;/strong&gt;, check the HTTP Referer header, if it is not starting with "&lt;strong&gt;http://a.com&lt;/strong&gt;" return the image "/&lt;strong&gt;content/images/no_hotlinking.png&lt;/strong&gt;", which is a prepared warning image like below:&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-16434886-f889-46fd-ab1e-3bbe6de7fedf.png" /&gt;&lt;/p&gt;
&lt;p&gt;Then, add this section into your &lt;strong&gt;web.config&lt;/strong&gt;, under &lt;strong&gt;system.webServer&lt;/strong&gt; node&lt;/p&gt;
&lt;pre class="language-markup"&gt;&lt;code&gt;&amp;lt;rewrite&amp;gt;
    &amp;lt;rules configSource="UrlRewrite.config" /&amp;gt;
&amp;lt;/rewrite&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;ASP.NET Core Applications&lt;/h2&gt;
&lt;hr /&gt;
&lt;p&gt;For ASP.NET Core applications, you don't need to install anything on your hosting environment.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Prepare a similar file under your application root&amp;nbsp;&lt;strong&gt;UrlRewrite.xml&lt;/strong&gt;, but with one difference: &lt;span style="color: #ff0000;"&gt;&lt;strong&gt;you have to add "rewrite" as root node.&lt;/strong&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre class="language-markup"&gt;&lt;code&gt;&amp;lt;rewrite&amp;gt;
  &amp;lt;rules&amp;gt;
    &amp;lt;rule name="Prevent Image Hotlinking"&amp;gt;
      &amp;lt;match url=".*\.(gif|jpg|png)$"/&amp;gt;
      &amp;lt;conditions&amp;gt;
        &amp;lt;add input="{HTTP_REFERER}" pattern="^$" negate="true"/&amp;gt;
        &amp;lt;add input="{HTTP_REFERER}" pattern="^http://a.com/.*$" negate="true"/&amp;gt;
      &amp;lt;/conditions&amp;gt;
      &amp;lt;action type="Rewrite" url="/images/no_hotlinking.png"/&amp;gt;
    &amp;lt;/rule&amp;gt;
  &amp;lt;/rules&amp;gt;
&amp;lt;/rewrite&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;span style="color: #ff0000;"&gt;&lt;strong&gt;Note: You have to set this file to always copy to output directory, otherwise it won't work!&lt;/strong&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre class="language-markup"&gt;&lt;code&gt;&amp;lt;ItemGroup&amp;gt;
  &amp;lt;None Update="UrlRewrite.xml"&amp;gt;
    &amp;lt;CopyToOutputDirectory&amp;gt;Always&amp;lt;/CopyToOutputDirectory&amp;gt;
  &amp;lt;/None&amp;gt;
&amp;lt;/ItemGroup&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Open your Startup.cs, add this code to&amp;nbsp;Configure method:&lt;/p&gt;
&lt;pre class="language-csharp"&gt;&lt;code&gt;public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    ...
    using (var urlRewriteStreamReader = File.OpenText("UrlRewrite.xml"))
    {
        var options = new RewriteOptions().AddIISUrlRewrite(urlRewriteStreamReader);
        app.UseRewriter(options);
    }
    ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then it will work the same way as the above ASP.NET Framework application.&lt;/p&gt;
&lt;h2&gt;Tips and Tricks&lt;/h2&gt;
&lt;hr /&gt;
&lt;p&gt;In real world cases, websites often have multiple domains with/out https and with/out port number. like:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;For production:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;http://a.com&lt;/li&gt;
&lt;li&gt;https://a.com&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;For development:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;http://dev.a.com&lt;/li&gt;
&lt;li&gt;https://localhost&lt;/li&gt;
&lt;li&gt;http://staging.a.com:5000&lt;/li&gt;
&lt;li&gt;...&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We don't have to add them one by one, we can use regular expressions :)&lt;/p&gt;
&lt;p&gt;For example, the rule for my blog website is this:&lt;/p&gt;
&lt;pre class="language-markup"&gt;&lt;code&gt;&amp;lt;rule name="Prevent Image Hotlinking"&amp;gt;
  &amp;lt;match url=".*\.(gif|jpg|png)$"/&amp;gt;
  &amp;lt;conditions&amp;gt;
    &amp;lt;add input="{HTTP_REFERER}" pattern="^$" negate="true"/&amp;gt;
    &amp;lt;add input="{HTTP_REFERER}" pattern="^https?:\/\/.*edi.wang:?\d{0,5}?\/.*$" negate="true"/&amp;gt;
    &amp;lt;add input="{HTTP_REFERER}" pattern="^https?:\/\/localhost:?\d{0,5}?\/.*$" negate="true"/&amp;gt;
    &amp;lt;add input="{HTTP_REFERER}" pattern="^https?:\/\/.*azurewebsites.net:?\d{0,5}?\/.*$" negate="true"/&amp;gt;
  &amp;lt;/conditions&amp;gt;
  &amp;lt;action type="Rewrite" url="/images/no_hotlinking.png"/&amp;gt;
&amp;lt;/rule&amp;gt;
&lt;/code&gt;&lt;/pre&gt;</content></entry><entry><id>b48ad5bc-b213-44ce-b499-fdc13cd5b6bb</id><title>Install Linux Subsystem on Windows 10 Fall Creators Update</title><updated>2017-11-25T07:59:04Z</updated><published>2017-11-25T07:59:04Z</published><link href="https://edi.wang/post/2017/11/25/install-linux-subsystem-on-windows-10-fcu" hreflang="en-us" /><author><name>Edi Wang</name><email>Edi.Wang@outlook.com</email></author><category term="Server and Tools" /><content>&lt;p&gt;In the recent Windows 10 v1709 Fall Creators Update, the Windows subsystem for Linux has been improved a lot. Let's see how easy it is now to install Linux on Windows.&lt;/p&gt;
&lt;h2&gt;1. Enable Windows Subsystem for Linux&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;Because the classic control panel has been hidden, now the quickest way to access &lt;strong&gt;Programs and Features&lt;/strong&gt; is to run&amp;nbsp;&lt;strong&gt;appwiz.cpl&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-f9564ba7-c600-4e0d-b3bf-729c94f88ba1.png" border="0" loading="lazy"&gt;&lt;/p&gt;
&lt;p&gt;And then check "&lt;strong&gt;Windows Subsystem for Linux&lt;/strong&gt;" under "&lt;strong&gt;Turn Windows features on or off&lt;/strong&gt;".&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-c1867c15-d406-48b9-bf86-56f12b275243.png" border="0" loading="lazy"&gt;&lt;/p&gt;
&lt;p&gt;Reboot your machine after this change.&lt;/p&gt;
&lt;h2&gt;2. Search for Linux releases in Microsoft Store&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;&lt;img src="/image/img-dd9a990d-0e9e-47be-9916-3490485ec388.png" border="0" loading="lazy"&gt;&lt;/p&gt;
&lt;p&gt;Currently, there are 3 types of Linux systems you can choose in the store.&amp;nbsp;&lt;strong&gt;Ubuntu&amp;nbsp;&lt;/strong&gt;and the other 2 versions of&amp;nbsp;openSUSE. I would prefer Ubuntu.&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-a1567119-8971-4add-822e-ef5bee0c71ed.png" border="0" loading="lazy"&gt;&lt;/p&gt;
&lt;p&gt;Install Ubuntu in the same way as an usual App. It will take some time, it's an OS after all.&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-8f246656-8438-4b7b-be3a-5a0b5202bfc4.png" border="0" loading="lazy"&gt;&lt;/p&gt;
&lt;h2&gt;3. Start and Use Ubuntu&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;When the installation finish, Ubuntu will appear on the Windows Start Menu, click to start.&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-355380eb-7d47-4e6e-b1c2-5b34b5942020.png" border="0" alt="" width="601" height="501" loading="lazy"&gt;&lt;/p&gt;
&lt;p&gt;The first time Ubuntu boots up, it can take some while.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-c6088b4a-6d23-450d-bee8-0935c3a72bc9.png" border="0" alt="" width="534" height="171" loading="lazy"&gt;&lt;/p&gt;
&lt;p&gt;And then set a pair of Linux username and password. Then you are good to go!&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-b8abd14a-9acf-40d8-9518-bcf851ab5d32.png" border="0" alt="" width="823" height="533" loading="lazy"&gt;&lt;/p&gt;
&lt;p&gt;You can use command line tools on this Ubuntu system. Even install .NET Core and start an ASP.NET Core website, and then access the website by a browser on your hosting Windows system:&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-6742f9b5-d226-4bb4-81fe-7de8204f0e89.png" border="0" alt="" width="823" height="658" loading="lazy"&gt;&lt;/p&gt;
&lt;p&gt;Refer to&amp;nbsp;&lt;a href="https://www.microsoft.com/net/download/linux-package-manager/ubuntu18-04/sdk-current" target="_blank" rel="noopener"&gt;https://www.microsoft.com/net/download/linux-package-manager/ubuntu18-04/sdk-current&lt;/a&gt; for detailed steps.&lt;/p&gt;</content></entry><entry><id>d64f1527-6b4a-4c2d-b0e3-8f89377b820f</id><title>How to Redirect Old Domain URL to New Domain in IIS</title><updated>2015-10-09T08:36:02Z</updated><published>2015-10-09T08:36:02Z</published><link href="https://edi.wang/post/2015/10/9/how-to-redirect-old-domain-url-to-new-domain-in-iis" hreflang="en-us" /><author><name>Edi Wang</name><email>Edi.Wang@outlook.com</email></author><category term="Server and Tools" /><content>&lt;h2&gt;Problem&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;When you change the domain name for your website, you are definitely going to solve the migration problem. You cannot just stop DNS on your old domain, because this will cause your indexed pages to be deleted by search engines. The correct way is to tell the search engine that you have a new domain name now, which is when the user accesses an old URL, redirect it to the new URL.&lt;/p&gt;
&lt;p&gt;Take my blog as an example, my old domain is &lt;strong&gt;d******k.com&lt;/strong&gt;, the new domain name is &lt;strong&gt;edi.wang&lt;/strong&gt;. So, for requests to the old domain, such as:&lt;/p&gt;
&lt;pre class="language-bash"&gt;&lt;code&gt;http://d******k/post/2015/4/22/how-to-read-webconfig-appsettings-with-bigiblity&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I need to redirect it to:&lt;/p&gt;
&lt;pre class="language-bash"&gt;&lt;code&gt;http://edi.wang/post/2015/4/22/how-to-read-webconfig-appsettings-with-bigiblity&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Which is just replace the domain name and keep everything else in the URL.&lt;/p&gt;
&lt;h2&gt;Solution&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;To implement this feature, we need to install the URL Rewrite module on IIS:&amp;nbsp;&lt;a href="http://www.iis.net/downloads/microsoft/url-rewrite"&gt;http://www.iis.net/downloads/microsoft/url-rewrite&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;For every website managed by IIS, it can assign URL rewrite or redirect rules individually. You can use both GUI and manually edit web.config to modify the rules.&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-66333d80-cb1d-4e8c-9c7c-ab6ef23616ea.png" border="0" alt="" width="836" height="605" loading="lazy"&gt;&lt;/p&gt;
&lt;p&gt;My configuration for migrating the domain name would be:&lt;/p&gt;
&lt;pre class="language-markup"&gt;&lt;code&gt;&amp;lt;system.webServer&amp;gt;
	&amp;lt;rewrite&amp;gt;
		&amp;lt;rules&amp;gt;
			&amp;lt;rule name="CanonicalHostNameRule"&amp;gt;
				&amp;lt;match url="^(.*)$" /&amp;gt;
				&amp;lt;conditions&amp;gt;
					&amp;lt;add input="{HTTP_HOST}" pattern="^d*******k.com$" /&amp;gt;
				&amp;lt;/conditions&amp;gt;
				&amp;lt;action type="Redirect" url="http://edi.wang/{R:1}" /&amp;gt;
			&amp;lt;/rule&amp;gt;
		&amp;lt;/rules&amp;gt;
	&amp;lt;/rewrite&amp;gt;
&amp;lt;/system.webServer&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The&lt;strong&gt; pattern&lt;/strong&gt; is using a regular expression, to check if &lt;strong&gt;{HTTP_HOST}&lt;/strong&gt; is equal to &lt;strong&gt;d*******k.com&lt;/strong&gt;.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;the &lt;strong&gt;{R:1}&lt;/strong&gt; in the &lt;strong&gt;action &lt;/strong&gt;means whatever is in the URL behind the domain name itself.&lt;/p&gt;
&lt;p&gt;We have two &lt;strong&gt;type&lt;/strong&gt;s: &lt;strong&gt;Redirect&lt;/strong&gt; and &lt;strong&gt;Rewrite&lt;/strong&gt;.&amp;nbsp;&lt;strong&gt;Redirect&lt;/strong&gt; will result in an HTTP 301, which will notify the search engine about the domain change, while &lt;strong&gt;Rewrite&lt;/strong&gt; will just keep the HTTP status code, for example, you have a "/a/b/c.html" and rewrite into "/d.html", then &lt;strong&gt;Rewrite&lt;/strong&gt; will just make it looks like you are accessing /d.html, and its content is output using c.html.&lt;/p&gt;
&lt;p&gt;If you have other rules, mind the order, they are being processed from top to bottom.&lt;/p&gt;
&lt;p&gt;You may refer to Microsoft documentation for more usage:&lt;a href="http://www.iis.net/learn/extensions/url-rewrite-module/url-rewrite-module-configuration-reference"&gt; http://www.iis.net/learn/extensions/url-rewrite-module/url-rewrite-module-configuration-reference&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Now, we can get a 301 redirection, the search engine will also update their index. After a couple of months, you can stop DNS on your old domain.&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-33127685-fbe9-4e37-ad87-db10d00d0bf9.png" border="0" alt="" width="1995" height="478" loading="lazy"&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;</content></entry><entry><id>052c996c-b053-4f73-a653-08a6baf33f3e</id><title>How to Manage Azure App Service in IIS Manager (inetmgr)</title><updated>2015-07-23T22:40:03Z</updated><published>2015-07-23T22:40:03Z</published><link href="https://edi.wang/post/2015/7/23/how-to-manage-azure-app-service-in-iis-manager-inetmgr" hreflang="zh-hans" /><author><name>Edi Wang</name><email>Edi.Wang@outlook.com</email></author><category term="Server and Tools" /><category term="Microsoft Azure" /><content>&lt;p&gt;The website we created on Azure seems can be only managed in the web portal or in the Server Explorer of Visual Studio. If we would like to use IIS to manage more powerful scenarios, seems VM is the only way. But that's not true.&lt;/p&gt;
&lt;p&gt;First, the IIS comes with Windows 8.1 can't connect to Azure by default. We need to install an extension &lt;a href="http://www.iis.net/downloads/microsoft/iis-manager"&gt;http://www.iis.net/downloads/microsoft/iis-manager&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;After install. Open your IIS manager, you will find a new menu item "Connect to a Site".&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-f1576df1-35d4-4e19-bbe6-b4664740a5aa.png" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;Enter your SCM address of the Azure Website in Server name, like:&lt;/p&gt;
&lt;p&gt;&amp;lt;yoursitename&amp;gt;&lt;span style="color: #ff0000;"&gt;.scm&lt;/span&gt;.azurewebsites.net:443&lt;/p&gt;
&lt;p&gt;Then, download the publish profile in Azure portal.&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-a07820cc-482e-4b91-aafe-22791abcffe2.png" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;You will find &lt;strong&gt;userName&lt;/strong&gt; and &lt;strong&gt;userPWD&lt;/strong&gt; in the profile for the connection.&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-8605bdad-8f4f-417d-9451-d7a5b5155769.png" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;Enter a name for your azure connection&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-301aa8d5-de6c-43da-aded-ef4e77a7cc3b.png" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;Check all modules for full management capabilities&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-3ca0b176-5de0-4719-989e-f56898936beb.png" alt="" /&gt;&lt;/p&gt;
&lt;p&gt;Now, you can manage your website in Azure with the IIS Manager you like.&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-d310980b-0d61-488f-8087-1c6b52088ed8.png" alt="" /&gt;&lt;/p&gt;</content></entry><entry><id>7c610694-2be5-424b-88ff-143aeeb86e5e</id><title>Azure Website Showing 404 for .woff Fonts in IE</title><updated>2015-05-19T09:24:08Z</updated><published>2015-05-19T09:24:08Z</published><link href="https://edi.wang/post/2015/5/19/azure-website-showing-404-for-woff-fonts-in-ie" hreflang="en-us" /><author><name>Edi Wang</name><email>Edi.Wang@outlook.com</email></author><category term=".NET" /><category term="Server and Tools" /><category term="Microsoft Azure" /><content>&lt;h2&gt;Problem&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;If you are using Website on Microsoft Azure (Currently renamed to Web Apps), you will find the .woff web font is not working in IE, it returns 404.&lt;/p&gt;
&lt;p&gt;Based on my experience, this is because IIS is not configured to use the correct MimeType. However, we can not operate the IIS on Azure, there's no RDP into an Azure Web Apps backend machine. How can we do that?&lt;/p&gt;
&lt;h2&gt;Solution&lt;/h2&gt;
&lt;hr&gt;
&lt;p&gt;In fact, after IIS7, the MineType is managed via web.config. So, we only need to add this code into &lt;code&gt;web.config/system.webServer&lt;/code&gt;, and IIS can now handle &lt;code&gt;.woff&lt;/code&gt; requests.&lt;/p&gt;
&lt;pre class="language-markup"&gt;&lt;code&gt;&amp;lt;staticContent&amp;gt; 
    &amp;lt;mimeMap fileExtension=".woff" mimeType="application/x-font-woff" /&amp;gt;
&amp;lt;/staticContent&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;By the way, the easiest way to deploy a hotfix on Azure is to use the Server Explorer to connect to Azure, open your website, find and edit the web.config file, it can be saved directly onto Azure.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-097256f1-71d5-42a0-a8e1-081c0c4870e3.png" border="0" alt="" width="1211" height="985" loading="lazy"&gt;&lt;/p&gt;</content></entry><entry><id>72b844b1-bff9-4cba-b62f-db43137ef531</id><title>Get Clear Password from IIS App Pool</title><updated>2015-02-10T04:17:07Z</updated><published>2015-02-10T04:17:07Z</published><link href="https://edi.wang/post/2015/2/10/get-clear-password-from-iis-apppool" hreflang="en-us" /><author><name>Edi Wang</name><email>Edi.Wang@outlook.com</email></author><category term="Server and Tools" /><content>&lt;p&gt;Sometimes, in order to get necessary permissions, we will assign domian accounts or local accounts for IIS app pool. For example, it usually happens for SharePoint deployment. However, this is not a secure way, because the password for these accounts are saved in clear text.&lt;/p&gt;
&lt;p&gt;For local accounts, the password is not readable. But if you are using domain accounts. The IIS Admin can read your password without any third party tools, just by CMD:&lt;/p&gt;
&lt;pre&gt;appcmd list apppool "App Pool Name" /text:*&lt;/pre&gt;
&lt;p&gt;The result is like this. Your clear password will be shown under [processModel] node. This is an example for a domain account.&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-09de3ab2-d581-45ec-8f51-7ff907249b54.png" alt="" width="747" height="397" /&gt;&lt;/p&gt;
&lt;p&gt;So far, there's no way to avoid clear password being stored in IIS. So I suggest do not use domain account for IIS if you can.&lt;/p&gt;</content></entry><entry><id>fca2c597-7db8-4014-a2fe-99a6f80c0bb1</id><title>Run Scheduled Tasks in ASP.NET Application</title><updated>2014-07-18T02:28:05Z</updated><published>2014-07-18T02:28:05Z</published><link href="https://edi.wang/post/2014/7/18/how-to-run-schedule-jobs-in-aspnet" hreflang="zh-hans" /><author><name>Edi Wang</name><email>Edi.Wang@outlook.com</email></author><category term=".NET" /><category term="Server and Tools" /><content>&lt;p&gt;在ASP.NET里运行定时任务，这是个老生常谈的话题了，撇开那些用per request搞定屌丝办法，目前最好的解决办法只有2种：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;如果你有大微软的Azure，可以直接在网站服务中找到Jobs，自己看一下就会了&lt;/li&gt;
&lt;li&gt;如果你是屌丝，买不起Azure，就用本文介绍的&lt;code&gt;WebBackgrounder&lt;/code&gt;搞&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;由于ASP.NET是服务器端Web框架，所以一般而言，一个操作的往往是只有收到客户端Request之后才能执行的，如果网站一直没人访问，没有Request进来，如何执行代码呢？定时任务就是这种坑爹场景。&lt;/p&gt;
&lt;p&gt;还好，大微软的MVC帝、ASP.NET小王子&lt;a href="http://haacked.com" target="_blank" rel="noopener noreferrer"&gt;haacked&lt;/a&gt;蜀黍给我们写了个&amp;nbsp;&lt;a href="http://www.nuget.org/packages/WebBackgrounder/"&gt;http://www.nuget.org/packages/WebBackgrounder/&lt;/a&gt;&amp;nbsp;专门捣鼓这种场景。作为一个屌丝程序猿，和大牛的区别就在于&amp;ldquo;好编程，不求甚解&amp;rdquo;。所以我们不必在意它是怎么实现的，只要会用就行了。我在公司的项目中就用了一下，感觉很爽。&lt;/p&gt;
&lt;p&gt;1. 首先，你要用高大上的ASP.NET 4.5&lt;/p&gt;
&lt;p&gt;为了装逼，我们要通过命令行安装&lt;code&gt;WebBackgrounder&lt;/code&gt;的NuGet包：&lt;/p&gt;
&lt;pre&gt;PM&amp;gt; Install-Package WebBackgrounder &lt;/pre&gt;
&lt;p&gt;2. 新建一个&lt;span style="color: #ff0000; background-color: #ffff00;"&gt;静态&lt;/span&gt;类，名字任意，我取名为&amp;nbsp;&lt;code&gt;WebBackgrounderSetup.cs&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;3. 在namespace外面，注意，&lt;span style="color: #ff0000; background-color: #ffff00;"&gt;一定一定要在namespace的外面&lt;/span&gt;！！！！！！加上这两行代码，用来注册在ASP.NET应用启动和结束时要触发的方法。&lt;/p&gt;
&lt;pre&gt;[assembly: WebActivatorEx.PostApplicationStartMethod(typeof(你的命名空间.WebBackgrounderSetup), "Start")]
[assembly: WebActivatorEx.ApplicationShutdownMethod(typeof(你的命名空间.WebBackgrounderSetup), "Shutdown")]&lt;/pre&gt;
&lt;p&gt;4. 根据你的需求，创建一个类，继承Job，比如我这个：&lt;/p&gt;
&lt;pre&gt;/// &amp;lt;summary&amp;gt;
/// Job To Refresh Project Tree into Memory
/// &amp;lt;/summary&amp;gt;
public class GetCucumberProjectTreeJob : Job
{
    public GetCucumberProjectTreeJob(TimeSpan interval, TimeSpan timeout)
        : base("GetCucumberProjectTree Job", interval, timeout)
    {
    }

    public override Task Execute()
    {
        return new Task(() =&amp;gt;
        {
            Logger.Debug("Refreshing Project Tree...");
            Utils.Projects = Utils.GetTreeFromDisk();
        });
    }
}&lt;/pre&gt;
&lt;p&gt;注意构造函数，是继承Job父类的，"GetCucumberProjectTree Job"是你的Job的名字，interval是每次执行间隔时间，timeout是单次操作的超时时间，如果超时，这次Job就会被撸掉。Execute方法里面的就是你要执行的代码，因为方法返回类型是个Task，所以要把你的代码包在Task里面。&lt;/p&gt;
&lt;p&gt;5. 把你创建的这个Job塞到刚才的WebBackgrounderSetup里面。代码看起来就像是这样：&lt;/p&gt;
&lt;pre&gt;public static class WebBackgrounderSetup
{
    static readonly JobManager _jobManager = CreateJobWorkersManager();

    public static void Start()
    {
        _jobManager.Start();
    }

    public static void Shutdown()
    {
        _jobManager.Dispose();
    }

    private static JobManager CreateJobWorkersManager()
    {
        var jobs = new IJob[]
        {
            new GetCucumberProjectTreeJob(TimeSpan.FromSeconds(5 * 60), TimeSpan.FromSeconds(20)),
        };

        var coordinator = new SingleServerJobCoordinator();
        var manager = new JobManager(jobs, coordinator);
        manager.Fail(ex =&amp;gt; Logger.Error("Web Job Blow Up.", ex));
        return manager;
    }
}&lt;/pre&gt;
&lt;p&gt;因为这里我只有一个Job要执行，所以IJobs[]数组里面只赛了一个对象。如果你有很多Job要调度，都可以往这个数字里塞。&lt;/p&gt;
&lt;p&gt;我需要每5分钟执行一次Job，每次超时20秒，就这样写：&lt;/p&gt;
&lt;pre&gt;new GetCucumberProjectTreeJob(TimeSpan.FromSeconds(5 * 60), TimeSpan.FromSeconds(20))&lt;/pre&gt;
&lt;p&gt;其中写5 * 60是个很流行的代码装逼方法，可以增加可读性，并且编译器在编译时候其实是把计算后的结果编译进去的，所以不会每次执行都去计算，即保住了逼格，也不影响性能。&lt;/p&gt;
&lt;p&gt;处理Job执行中的异常，用的是&lt;/p&gt;
&lt;pre&gt;manager.Fail&lt;/pre&gt;
&lt;p&gt;里面塞的是一个委托，take的是爆破的Exception对象，在这里我只是记录一下Log，你可以自由发挥。&lt;/p&gt;
&lt;p&gt;6. 有图有真相：&lt;/p&gt;
&lt;p&gt;&lt;img src="/image/img-5b394358-da2e-4a0a-ab32-a740123a4424.png" border="0" alt="" width="755" height="505" loading="lazy"&gt;&lt;/p&gt;</content></entry></feed>