{"id":1170,"date":"2024-02-23T06:04:37","date_gmt":"2024-02-23T14:04:37","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/powershell-community\/?p=1170"},"modified":"2024-02-23T06:04:37","modified_gmt":"2024-02-23T14:04:37","slug":"creating-a-scalable-customised-running-environment","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/powershell-community\/creating-a-scalable-customised-running-environment\/","title":{"rendered":"Creating a scalable, customised running environment"},"content":{"rendered":"<p>Often people come to PowerShell as a developer looking for a simpler life, or as a support person looking to make their life easier. Either way, we start exploring ways to encapsulate repeatable functionality, and through PowerShell that is cmdlets.<\/p>\n<p>How to create these is defined well in <a href=\"https:\/\/devblogs.microsoft.com\/powershell-community\/designing-powershell-for-end-users\/\">Designing PowerShell For End Users<\/a>. And Microsoft obviously have pretty good documention, including <a href=\"https:\/\/learn.microsoft.com\/en-us\/powershell\/scripting\/developer\/module\/how-to-write-a-powershell-script-module\">How to Write a PowerShell Script Module<\/a>. I also have a few basic rules I remember wehen creating cmdlets to go along with the above posts:<\/p>\n<ul>\n<li>Always use cmdlet binding.<\/li>\n<li>Call the file name the same as the cmdlet, without the dash.<\/li>\n<\/ul>\n<p>But how do you organise them and ensure that they always load. This post outlines an approach that has worked well for me across a few different jobs, with a few iterations to get to this point.<\/p>\n<h2>Methods<\/h2>\n<p>There are 2 parts to making an effective running environment<\/p>\n<ul>\n<li>Ensuring all your cmdlets for a specific module will load.<\/li>\n<li>Ensuring all your modules will load.<\/li>\n<\/ul>\n<h3>Example setup<\/h3>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/powershell-community\/wp-content\/uploads\/sites\/69\/2024\/02\/ModuleSetup-2.png\" alt=\"folder-structure\" \/><\/p>\n<p>We are aiming high here. Over time your functionality will grow and this shows a structure that allows for growth. There are 3 modules (effectively folders): <code>Forms<\/code>, <code>BusinessUtilities<\/code> and <code>GeneralUtilities<\/code>. They are broken up into 2 main groupings, <code>my-support<\/code> and <code>my-utilities<\/code>. <a href=\"https:\/\/github.com\/rod-meaney\/ps-community-blog\">ps-community-blog<\/a> is the GitHub repository where you can find this example.<\/p>\n<p>Inside the <code>GenreralUtilities<\/code> folder you can see the all-important <code>.psm1<\/code>, with the same name as the folder and a couple of cmdlets I have needed over the years. The <code>.psm1<\/code> file is a requirement to create a module.<\/p>\n<h2>Ensuring all your cmdlets for a specific module will load<\/h2>\n<p>Most descriptions of creating modules will explain that you need to either add the cmdlet into the <code>.psm1<\/code>, or load the cmdlet files in the <code>.psm1<\/code> file. Instead, put the below in ALL your <code>.psm1<\/code> module files:<\/p>\n<pre><code class=\"language-powershell\">Get-ChildItem -Path \"$PSScriptRoot\\*.ps1\" | ForEach-Object {\n    . $PSScriptRoot\\$($_.Name)\n}<\/code><\/pre>\n<p>What does this do and why does it work?<\/p>\n<ul>\n<li>At a high level, it iterates over the current folder, and runs every <code>.ps1<\/code> file as PowerShell.<\/li>\n<li><code>$PSScriptRoot<\/code> is the key here, and tells running session, what the location of the current code is.<\/li>\n<\/ul>\n<p>This means you can create cmdlets under this structure, and they will automatically load when you start up a new PowerShell session.<\/p>\n<h2>Ensuring all your modules will load<\/h2>\n<p>So, the modules are sorted. How do we make sure the modules themselves load? It&#8217;s all about the <code>Profile.ps1<\/code>. You will either find it or need to create it in:<\/p>\n<ul>\n<li>PowerShell 5 and lower &#8211; <code>$HOME\\Documents\\WindowsPowerShell\\Profile.ps1<\/code>.<\/li>\n<li>PowerShell 7 &#8211; <code>$HOME\\Documents\\PowerShell\\Profile.ps1<\/code>.<\/li>\n<li>For detailed information, see <a href=\"https:\/\/learn.microsoft.com\/en-us\/powershell\/module\/microsoft.powershell.core\/about\/about_profiles\">About Profiles<\/a>.<\/li>\n<\/ul>\n<p>So this file runs at the start of every session that is opened on your machine. I have included both 5 and 7, as in a lot of corporate environments, 5 is all that is available, and often people don&#8217;t have access to modify their environment. With some simple code we can ensure our modules will open. Add this into your <code>Profile.ps1<\/code>:<\/p>\n<pre><code class=\"language-powershell\">Write-Host \"Loading Modules for Day-to-Day use\"\n$ErrorActionPreference = \"Stop\" # A safeguard for any errors\n\n$MyModuleDef = @{\n    Utilities = @{\n        Path    = \"C:\\work\\git-personal\\ps-community-blog\\my-utilities\"\n        Exclude = @(\".git\")\n    }\n    Support = @{\n        Path    = \"C:\\work\\git-personal\\ps-community-blog\\my-support\"\n        Exclude = @(\".git\")\n    }\n}\n\nforeach ($key in $MyModuleDef.Keys) {\n    $module  = $MyModuleDef[$key]\n    $exclude = $module.Exclude\n\n    $env:PSModulePath = @(\n        $env:PSModulePath\n        $module.Path\n    ) -join [System.IO.Path]::PathSeparator\n\n    Get-ChildItem -Path $module.Path -Directory -Exclude $exclude |\n        ForEach-Object {\n            Write-Host \"Loading Module $($_.Name) in $Key\"\n            Import-Module $_.Name\n        }\n}<\/code><\/pre>\n<p>What does this do and why does it work?<\/p>\n<ul>\n<li>At a high level, it defines your module groupings, then loads your modules into the PowerShell session.<\/li>\n<li><code>$MyModuleDef<\/code> contains the reference to your module groupings, to make sure all the sub folders are loaded as modules.<\/li>\n<li><code>Exclude<\/code> is very important. You may load the code directly of your code base, so ignoring those as modules is important. I have also put DLL&#8217;s in folders in module groupings, and ignoring these is important as well.<\/li>\n<\/ul>\n<p>Now, every time you open any PowerShell session on your machine, all your local cmdlets will be there, ready to use with all the wonderful functionality you have created.<\/p>\n<h2>Conclusion<\/h2>\n<p>Having your own PowerShell cmdlets at your fingertips with minimal overhead or thinking makes your PowerShell experinece so very much more rewarding. It also makes it easier to do as I like to do and start the day with my favourite mantra:<\/p>\n<blockquote>\n<p>Lets break some stuff!<\/p>\n<\/blockquote>\n<p><!-- link references --><\/p>\n","protected":false},"excerpt":{"rendered":"<p>This post shows how to create an easy to support environment with all your own cmdlets.<\/p>\n","protected":false},"author":112698,"featured_media":77,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[13,2],"tags":[83,3,85,86],"class_list":["post-1170","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-powershell","category-powershell-community","tag-automation","tag-powershell","tag-toolmaking","tag-user-experience"],"acf":[],"blog_post_summary":"<p>This post shows how to create an easy to support environment with all your own cmdlets.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/powershell-community\/wp-json\/wp\/v2\/posts\/1170","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/powershell-community\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/powershell-community\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/powershell-community\/wp-json\/wp\/v2\/users\/112698"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/powershell-community\/wp-json\/wp\/v2\/comments?post=1170"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/powershell-community\/wp-json\/wp\/v2\/posts\/1170\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/powershell-community\/wp-json\/wp\/v2\/media\/77"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/powershell-community\/wp-json\/wp\/v2\/media?parent=1170"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/powershell-community\/wp-json\/wp\/v2\/categories?post=1170"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/powershell-community\/wp-json\/wp\/v2\/tags?post=1170"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}