├── .github ├── CODE_OF_CONDUCT.md ├── ISSUE_TEMPLATE │ ├── New_Post_Idea.md │ ├── Post_Issue_Question.md │ ├── Request_Update.md │ └── config.yml ├── PULL_REQUEST_TEMPLATE.md └── SECURITY.md ├── .gitignore ├── .markdownlint-cli2.yaml ├── .markdownlint.yaml ├── .vale.ini ├── .vscode ├── cSpell.json ├── cspell │ ├── blog │ │ ├── cspell.yaml │ │ └── dictionaries │ │ │ └── blog-terms.txt │ └── psdocs │ │ ├── cspell.yaml │ │ └── dictionaries │ │ ├── externalCommands.txt │ │ ├── fictionalCorps.txt │ │ ├── fileExtensions.txt │ │ ├── psdocs.txt │ │ └── pwshAliases.txt └── extensions.json ├── Docs ├── Becoming-a-contributor.md ├── Creating-a-new-post.md ├── GitHub-workflow-for-new-post.md ├── Ideas-for-Posts.md ├── Markdown-cheatsheet.md ├── README.md ├── Reviewers-Guide.md ├── Setup-GitHub-for-Local-Workflow.md ├── Submitting-a-PR.md └── media │ ├── Becoming-a-contributor │ └── blog-login.png │ ├── GitHub-workflow-for-new-post │ └── Blog-gitflow.png │ ├── Markdown-cheetsheet │ └── alerts.png │ └── Setup-GitHub-for-Local-Workflow │ ├── clone-or-download.png │ ├── edit-article.png │ ├── fork.png │ ├── git-and-github-initial-setup.png │ ├── gitbash-start.png │ ├── github-2fa.png │ └── github-login.png ├── LICENSE ├── Posts ├── 2021 │ ├── 10 │ │ ├── Expresso.png │ │ ├── crescendo-configuration.md │ │ ├── crescendo-output-handler.md │ │ ├── crescendo-parser.png │ │ ├── netstat-output.png │ │ ├── parsing-netstat.md │ │ └── tfl-secrets.md │ ├── 11 │ │ └── tfl-formatenumeration.md │ ├── 12 │ │ ├── jon-1.png │ │ ├── jon-2.png │ │ ├── jon-preferences.md │ │ ├── media │ │ │ └── tfl-preview │ │ │ │ ├── after.png │ │ │ │ └── before.png │ │ ├── tfl-params.md │ │ └── tfl-preview.md │ ├── 02 │ │ ├── Announcing-Community-Blog.md │ │ ├── Article.0001.ChangeDriveLetter.md │ │ ├── Coming-Soon.md │ │ ├── Get-Services-PS7-VS-PS5.1.md │ │ ├── ScriptingGuy.0002.GetDateOfYesterday.md │ │ ├── XML-NewEmployees.md │ │ └── media │ │ │ └── Announcing-Community-Blog │ │ │ └── doctordns-tweet.png │ ├── 03 │ │ ├── Caps-lock-ToUpper.md │ │ ├── DarwinJS-Contribution-LightningFastandEasyProvisioningofGitwithSSHKeyAuthenticationonWindows.md │ │ ├── File-System-Watcher-Engine-Event.md │ │ ├── ScriptingGuy.0006.readingbottomup.md │ │ ├── ScriptingGuy.0007.folderexists.md │ │ └── media │ │ │ └── DarwinJS-Contribution-LightningFastandEasyProvisioningofGitwithSSHKeyAuthenticationonWindows │ │ │ └── windows-git-ssh.png │ ├── 04 │ │ ├── NewTemporaryFolders.md │ │ ├── get-live-servers.md │ │ └── tfl-IsUserALocalAdministrator.md │ ├── 05 │ │ ├── SendingDataToTheClipBoard.md │ │ ├── media │ │ │ └── SendingDataToTheClipBoard │ │ │ │ └── InteractivePromptStoppingTheOldSolution.PNG │ │ ├── tfl-WMIEventHandler.md │ │ └── tfl-output_as_string.md │ ├── 06 │ │ ├── media │ │ │ └── tfl-edgestart │ │ │ │ └── tfl-edgestgart.png │ │ ├── tfl-edgestart.md │ │ └── tfl-rename-nic.md │ ├── 07 │ │ └── tfl-regkey.md │ ├── 08 │ │ └── How Can I Be Notified Any Time a Service Goes Down.md │ └── 09 │ │ ├── Understanding Get-ACL and AD Drive Output.md │ │ ├── media │ │ └── getaclad │ │ │ ├── ExtADPermission.png │ │ │ ├── InheritanceType_In_GUI.png │ │ │ ├── InheritedObjectType.png │ │ │ └── applythiscontainer.png │ │ ├── my-crescendo-journey.md │ │ └── tfl-profile.md ├── 2022 │ ├── 07 │ │ ├── PasswordExpiryNotificationUsingTeamsandGraphAPI.md │ │ ├── ReadCMStatusMessages.md │ │ ├── cheat-sheet-console-experience.md │ │ ├── explorer.png │ │ ├── format-list.png │ │ ├── format-table.png │ │ ├── prompt.png │ │ └── tab-completion.png │ ├── 08 │ │ ├── Get-CimInstance_autoComplete.png │ │ ├── Invoke-CimMethod_autoComplete.png │ │ ├── NamespaceManiputlation.png │ │ └── many-wmi-flavours.md │ └── 09 │ │ └── Registry-Monitor.md ├── 2023 │ ├── 11 │ │ ├── automate-text-summarization-with-openai-powershell.md │ │ └── powershell-twilio-contact-tracing-communication.md │ ├── 01 │ │ └── Mastering-The-Steppable-Pipeline.md │ ├── 03 │ │ └── Update-XML-File-using-PowerShell.md │ ├── 04 │ │ ├── Convert-Specific-Sheet-Of-Excel-Into-Json-Using-PowerShell.md │ │ └── media │ │ │ └── Convert-Specific-Sheet-Of-Excel-Into-Json-Using-PowerShell │ │ │ └── Image-MultipleTablesInOneSheet.png │ ├── 05 │ │ ├── Designing-For-User-Experience-In-PowerShell.md │ │ ├── Measuring-Script-Execution-Time.md │ │ ├── Media │ │ │ └── Porting-GeneratePassword-From-Csharp │ │ │ │ ├── File-OpenFromGAC.png │ │ │ │ ├── GeneratePasswordMethod.png │ │ │ │ ├── MembershipClass.png │ │ │ │ ├── OpenFromGACMenu.png │ │ │ │ └── Result.png │ │ └── Porting-GeneratePassword-From-Csharp.md │ ├── 06 │ │ └── Measuring-Download-Time.md │ └── 07 │ │ ├── Changing-Console-Title.md │ │ └── Media │ │ └── WindowTitle.png └── 2024 │ ├── 02 │ ├── Media │ │ └── creating-a-scalable-customised-running-environment │ │ │ └── ModuleSetup.png │ └── creating-a-scalable-customised-running-environment.md │ ├── 03 │ ├── Media │ │ └── simple-form-development-using-powershell │ │ │ ├── JsonAndCmdlet.png │ │ │ └── LaunchingASimpleForm.png │ └── simple-form-development-using-powershell.md │ └── 04 │ ├── Media │ └── encrypting-secrets-locally │ │ └── KeyValueStore.png │ └── encrypting-secrets-locally.md └── README.md /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Microsoft Open Source Code of Conduct 2 | 3 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 4 | 5 | Resources: 6 | 7 | - [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/) 8 | - [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) 9 | - Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns 10 | - Employees can reach out at [aka.ms/opensource/moderation-support](https://aka.ms/opensource/moderation-support) 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/New_Post_Idea.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: New Post Idea 🚀 3 | about: Suggest a topic for a new blog post 4 | title: Post idea 5 | labels: post-idea 6 | assignees: '' 7 | --- 8 | 9 | # Summary of the new document or enhancement 10 | 11 | 16 | 17 | Details of requested document: 18 | 19 | - Are you offering to write the post [Y/N]: 20 | - Proposed title: 21 | - Links to related posts: 22 | - Description of the problem/scenario/question you want covered: 23 | 24 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/Post_Issue_Question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Post Issue 📚 3 | about: Problems with an existing post 4 | title: Post Issue 5 | labels: post-issue 6 | assignees: '' 7 | --- 8 | 13 | # Problem description or question 14 | 15 | - Link to the blog post: 16 | - Description of the problem or question: 17 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/Request_Update.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Request update to old post from The Scripting Blog 🆕 3 | about: Update an outdated post from the old blog 4 | title: Update request 5 | labels: post-update 6 | assignees: '' 7 | --- 8 | 12 | 13 | # Summary of the update request 14 | 15 | - Link to the post in the old blog: 16 | - Description of what needs to be changed: 17 | 18 | 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | # PR Summary 2 | 3 | 4 | 5 | ## PR Checklist 6 | 7 | - Do you want this post to be published on a specific date?: Y/N [Specify date]: 8 | - [ ] I have read the [contributors guide][contrib] and followed the style and process guidelines 9 | - [ ] PR has a meaningful title 10 | - [ ] Includes content related an open issue 11 | - see [Closing issues using keywords][key]. 12 | - [ ] This PR is ready to merge and is not **Work in Progress** 13 | 14 | 15 | [contrib]: https://docs.microsoft.com/powershell/scripting/community/contributing/overview 16 | [key]: https://help.github.com/en/articles/closing-issues-using-keywords 17 | -------------------------------------------------------------------------------- /.github/SECURITY.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Security 4 | 5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin) and [PowerShell](https://github.com/PowerShell). 6 | 7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/security.md/definition), please report it to us as described below. 8 | 9 | ## Reporting Security Issues 10 | 11 | **Please do not report security vulnerabilities through public GitHub issues.** 12 | 13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/security.md/msrc/create-report). 14 | 15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/security.md/msrc/pgp). 16 | 17 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc). 18 | 19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: 20 | 21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) 22 | * Full paths of source file(s) related to the manifestation of the issue 23 | * The location of the affected source code (tag/branch/commit or direct URL) 24 | * Any special configuration required to reproduce the issue 25 | * Step-by-step instructions to reproduce the issue 26 | * Proof-of-concept or exploit code (if possible) 27 | * Impact of the issue, including how an attacker might exploit the issue 28 | 29 | This information will help us triage your report more quickly. 30 | 31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/security.md/msrc/bounty) page for more details about our active programs. 32 | 33 | ## Preferred Languages 34 | 35 | We prefer all communications to be in English. 36 | 37 | ## Policy 38 | 39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/security.md/cvd). 40 | 41 | 42 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/styles 2 | .vale/ 3 | -------------------------------------------------------------------------------- /.markdownlint-cli2.yaml: -------------------------------------------------------------------------------- 1 | # Rule definitions live in .markdownlint.json 2 | 3 | # Include a custom rule package 4 | # customRules: 5 | # - markdownlint-rule-titlecase 6 | 7 | # Fix any fixable errors 8 | fix: true 9 | 10 | # Define a custom front matter pattern 11 | # frontMatter: (^---\s*$[^]*?^---\s*$)(\r\n|\r|\n|$) 12 | 13 | # Define glob expressions to use (only valid at root) 14 | # globs: 15 | # - "!*bout.md" 16 | 17 | # Define glob expressions to ignore 18 | ignores: 19 | - .vscode 20 | - assets 21 | - tests 22 | - tools 23 | 24 | # Use a plugin to recognize math 25 | # markdownItPlugins: 26 | # - - "@iktakahiro/markdown-it-katex" 27 | 28 | # Disable inline config comments 29 | noInlineConfig: false 30 | 31 | # Disable progress on stdout (only valid at root) 32 | noProgress: true 33 | 34 | # Use a specific formatter (only valid at root) 35 | # outputFormatters: 36 | # - [markdownlint-cli2-formatter-default] 37 | -------------------------------------------------------------------------------- /.markdownlint.yaml: -------------------------------------------------------------------------------- 1 | MD001: true # header-increment 2 | # MD002 # first-header-h1 - Superceded by MD041 3 | MD003: # header-style 4 | style: atx 5 | MD004: # ul-style 6 | style: dash 7 | MD005: true # list-indent 8 | # MD006 # ul-start-left - Superceded by MD007's start_indented option 9 | MD007: # ul-indent 10 | indent: 2 11 | start_indented: false 12 | # MD008 # Removed from linter; used to specify indentation for ul 13 | MD009: # no-trailing-spaces 14 | br_spaces: 2 15 | strict: true 16 | MD010: true # no-hard-tabs 17 | MD011: true # no-reversed-links 18 | MD012: true # no-multiple-blanks 19 | MD013: # line-length 20 | code_block_line_length: 90 21 | code_blocks: true 22 | heading_line_length: 100 23 | headings: true 24 | line_length: 100 25 | stern: true 26 | tables: false 27 | MD014: true # commands-show-output 28 | # MD015 # "Use of non-atx style headers" - Removed from linter, replaced by MD003 29 | # MD016 # "Use of non-closed-atx style headers" - Removed from linter, replaced by MD003 30 | # MD017 # "Use of non-setext style headers" - Removed from linter, replaced by MD003 31 | MD018: true # no-missing-space-atx 32 | MD019: true # no-multiple-space-atx 33 | MD020: true # no-missing-space-closed-atx 34 | MD021: true # no-multiple-space-closed-atx 35 | MD022: true # blanks-around-headers 36 | MD023: true # header-start-left 37 | MD024: # no-duplicate-header 38 | siblings_only: true 39 | MD025: # single-h1 40 | front_matter_title: '' 41 | level: 1 42 | MD026: # no-trailing-punctuation 43 | punctuation: '.,;:!。,;:!?' 44 | MD027: true # no-multiple-space-blockquote 45 | MD028: true # no-blanks-blockquote 46 | MD029: # ol-prefix 47 | style: one 48 | MD030: true # list-marker-space 49 | MD031: true # blanks-around-fences 50 | MD032: true # blanks-around-lists 51 | MD033: # no-inline-html 52 | allowed_elements: 53 | - a 54 | - br 55 | - code 56 | - kbd 57 | - li 58 | - properties 59 | - sup 60 | - tags 61 | - ul 62 | MD034: true # no-bare-urls 63 | MD035: # hr-style 64 | style: '---' 65 | MD036: true # no-emphasis-as-header 66 | MD037: true # no-space-in-emphasis 67 | MD038: true # no-space-in-code 68 | MD039: true # no-space-in-links 69 | MD040: false # fenced-code-language 70 | MD041: false # first-line-h1 71 | MD042: true # no-empty-links 72 | MD043: false # required-headers 73 | MD044: # proper-names 74 | code_blocks: false 75 | names: 76 | - PowerShell 77 | - IntelliSense 78 | - Authenticode 79 | - CentOS 80 | - Contoso 81 | - CoreOS 82 | - Debian 83 | - Ubuntu 84 | - openSUSE 85 | - RHEL 86 | - JavaScript 87 | - .NET 88 | - NuGet 89 | - VS Code 90 | - Newtonsoft 91 | MD045: true # no-alt-text 92 | MD046: # code-block-style 93 | style: fenced 94 | MD047: true # single-trailing-newline 95 | MD048: # code-fence-style 96 | style: backtick 97 | MD049: # emphasis-style 98 | style: underscore 99 | MD050: # strong-style 100 | style: asterisk 101 | MD051: true # link-fragments 102 | MD052: true # reference-links-images 103 | MD053: true # link-image-reference-definitions 104 | -------------------------------------------------------------------------------- /.vale.ini: -------------------------------------------------------------------------------- 1 | StylesPath = .vscode/styles 2 | Packages = https://microsoft.github.io/Documentarian/packages/vale/PowerShell-Docs.zip 3 | MinAlertLevel = suggestion 4 | 5 | [*] 6 | BasedOnStyles = PowerShell-Docs 7 | Vale.Spelling = NO # We use cspell 8 | 9 | [*.md] 10 | BasedOnStyles = PowerShell-Docs 11 | -------------------------------------------------------------------------------- /.vscode/cSpell.json: -------------------------------------------------------------------------------- 1 | { 2 | "import": [ 3 | "./cspell/psdocs/cspell.yaml", 4 | "./cspell/blog/cspell.yaml" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /.vscode/cspell/blog/cspell.yaml: -------------------------------------------------------------------------------- 1 | name: blog 2 | description: > 3 | This cSpell configuration is for terminology used on the Blog. Only add to the dictionary defined 4 | here if the terms aren't for general PowerShell. If they apply to PowerShell more broadly, 5 | contribute those changes back to the psdocs configuration and dictionaries in the 6 | MicrosoftDocs/PowerShell-Docs repository, not here. 7 | 8 | dictionaryDefinitions: 9 | - name: blog-terms 10 | description: > 11 | Dictionary of common terms used in the blog.. Add entries to this dictionary for words used 12 | in blog posts but not broader PowerShell or other documentation. 13 | path: ./dictionaries/blog-terms.txt 14 | 15 | # These settings are applied to combinations of language (file type) and locale. For any given file 16 | # and locale, all matching dictionaries are applied. 17 | languageSettings: 18 | # Any Markdown file 19 | - languageId: markdown 20 | locale: '*' 21 | dictionaries: 22 | - blog-terms 23 | - languageId: yaml 24 | locale: '*' 25 | dictionaries: 26 | - blog-terms 27 | -------------------------------------------------------------------------------- /.vscode/cspell/blog/dictionaries/blog-terms.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PowerShell/Community-Blog/7837d320a6bd85ca7d4469dcfaa115112333fac0/.vscode/cspell/blog/dictionaries/blog-terms.txt -------------------------------------------------------------------------------- /.vscode/cspell/psdocs/cspell.yaml: -------------------------------------------------------------------------------- 1 | enabled: true 2 | description: > 3 | This configuration defines the default spellcheck settings for all PowerShell documentation. Where 4 | needed, other projects and subfolders can extend or override these defaults. 5 | 6 | # Ensure that any comments in code files to controll cSpell are correct. 7 | validateDirectives: true 8 | 9 | # These apply to all files unless otherwise specified. They're defined in NPM modules that are 10 | # available by default with the extension. 11 | dictionaries: 12 | - azureTerms 13 | - companies 14 | - filetypes 15 | - misc 16 | - powershell 17 | - softwareTerms 18 | 19 | # These are locally defined. They must be specified for the document type they're used in. 20 | dictionaryDefinitions: 21 | - name: externalCommands 22 | description: > 23 | Dictionary of common commands external to PowerShell. Add entries to this dictionary for 24 | commands, services, and script keywords that are referenced in documentation but are not 25 | specific to or included in PowerShell. 26 | path: ./dictionaries/externalCommands.txt 27 | - name: fictionalCorps 28 | description: > 29 | Dictionary of fictional company names used in documentation. Add entries to this dictionary 30 | when using a valid but nonexistant fictional company or organization. 31 | path: ./dictionaries/fictionalCorps.txt 32 | - name: fileExtensions 33 | description: > 34 | Dictionary of file extensions referenced in documentation. Add entries to this dictionary 35 | when a valid file extension is marked as an unknown spelling. 36 | path: ./dictionaries/fileExtensions.txt 37 | - name: psdocs 38 | description: > 39 | General PowerShell documentation dictionary. Add entries to this dictionary for PowerShell 40 | concepts, terms, or other names. Consider submitting them to the upstream PowerShell 41 | dictionary if sensible. 42 | path: ./dictionaries/psdocs.txt 43 | - name: pwshAliases 44 | description: > 45 | Dictionary of PowerShell aliases. Add entries to this dictionary for command and parameter 46 | aliases to keep the main dictionary easier to use. 47 | path: ./dictionaries/pwshAliases.txt 48 | 49 | # Defining patterns here makes it easier to understand the definitions for the ignore and include 50 | # pattern lists (`*RegExpList`). Also allows us to document these patterns to some degree. 51 | patterns: 52 | - name: domain-azure-edge 53 | description: Ignore misspellings caused by lowercase domain names for Azure edge domains. 54 | pattern: /\S+\.azureedge\.net/ 55 | - name: domain-windows-blob 56 | description: Ignore misspellings caused by lowercase domain names Windows blob storage domains 57 | pattern: /\S+\.blob\.core\.windows\.net/ 58 | - name: domain-gallery 59 | description: Ignore segments preceeding or following the powershellgallery domain name. 60 | pattern: /(\S+\.)?powershellgallery\.com(\S+)?/ 61 | - name: domains 62 | description: Ignore apparent misspellings as components of well-known domain name. 63 | pattern: 64 | - domain-azure-edge 65 | - domain-gallery 66 | - domain-windows-blob 67 | 68 | - name: markdown-code-block-output 69 | description: Ignore text in output code blocks. 70 | pattern: '/(?:```[oO]utput[\s\S]*?```)/g' 71 | - name: markdown-code-block-syntax 72 | description: Ignore text in output code blocks. 73 | pattern: '/(?:```[sS]yntax[\s\S]*?```)/g' 74 | - name: markdown-code-blocks 75 | description: Don't check spelling in output or syntax blocks. 76 | pattern: 77 | - markdown-code-block-output 78 | - markdown-code-block-syntax 79 | 80 | - name: markdown-link-reference 81 | description: Matches 'foobar' in '[foo bar][foobar]' 82 | pattern: /(?<=\])\[[^\]]+\]/ 83 | - name: markdown-link-inline 84 | description: Matches '/foo/bar' in '[foo bar](/foo/bar)' 85 | pattern: '/(?<=\])\([^\)]+\)/' 86 | - name: markdown-link-definition 87 | description: "Matches '/foo/bar' in '[foobar]: /foo/bar'" 88 | pattern: '/(?<=\]:\s)(\s*((https?:)?|\/|\.{1,2}))(\/\S+)/' 89 | - name: markdown-links 90 | description: Don't check link definitions or references. 91 | pattern: 92 | - markdown-link-inline 93 | - markdown-link-reference 94 | - markdown-link-definition 95 | 96 | - name: registry-paths 97 | description: Ignore Windows registry paths 98 | pattern: /(HK(CR|CU|LM))(:\S*)?/ 99 | 100 | - name: wildcard-fragment-prefix 101 | description: Ignore misspellings caused by partial words with a wildcard at the start. 102 | pattern: '/[^\*]\*\w+/' 103 | - name: wildcard-fragment-suffix 104 | description: Ignore misspellings caused by partial words with a wildcard at the end. 105 | pattern: '/\w+\*[^\*]/' 106 | - name: wildcard-fragments 107 | pattern: 108 | - wildcard-fragment-prefix 109 | - wildcard-fragment-suffix 110 | 111 | # Any patterns listed here are ignored for spellcheck. 112 | # 113 | # We ignore the URLs for inline markdown links, Markdown link references, and Markdown link 114 | # reference definitions because these will otherwise be very noisy and they're not displayed to 115 | # readers anyway. 116 | # 117 | # We ignore the spelling for all text in output code blocks for Markdown files because that text 118 | # represents output from real commands and any spelling errors are not a fault in the documentation. 119 | # 120 | # We ignore registry paths, wildcard fragments, and components of well-known domains because those 121 | # are intentionally or uncontrollably downcased or "incorrect" spellings. 122 | ignoreRegExpList: 123 | - domains 124 | - markdown-code-blocks 125 | - markdown-links 126 | - registry-paths 127 | - wildcard-fragments 128 | 129 | # The default locale for this documentation is US English. 130 | language: 'en,en-US' 131 | 132 | # These settings are applied to combinations of language (file type) and locale. For any given file 133 | # and locale, all matching dictionaries are applied. 134 | languageSettings: 135 | # Any file written in English 136 | - languageId: '*' 137 | locale: en 138 | dictionaries: 139 | - wordsEn 140 | # Any file written in US English 141 | - languageId: '*' 142 | locale: en-US 143 | dictionaries: 144 | - wordsEn 145 | # Any file written in British English 146 | - languageId: '*' 147 | locale: en-GB 148 | dictionaries: 149 | - wordsEnGb 150 | # Any Markdown file 151 | - languageId: markdown 152 | locale: '*' 153 | dictionaries: 154 | - externalCommands 155 | - fictionalCorps 156 | - fileExtensions 157 | - psdocs 158 | - pwshAliases 159 | -------------------------------------------------------------------------------- /.vscode/cspell/psdocs/dictionaries/externalCommands.txt: -------------------------------------------------------------------------------- 1 | AcroRd32 2 | Appinfo 3 | appwiz 4 | audiodg 5 | Audiosrv 6 | azurecli 7 | ccmsetup 8 | cd 9 | certreq 10 | chdir 11 | conhost 12 | csrss 13 | distro 14 | dpkg 15 | elif 16 | filesrv 17 | ftype 18 | gedit 19 | iexplore 20 | iisadmin 21 | Intune 22 | kubectl 23 | LanmanServer 24 | LanmanWorkstation 25 | LSASRV 26 | lsass 27 | makecert 28 | mkdir 29 | mscorlib 30 | msdtc 31 | msseces 32 | netcoreapp 33 | Netlogon 34 | netsh 35 | netsvcs 36 | Pandoc 37 | rdpclip 38 | RSAT 39 | rstrui 40 | setenv 41 | svchost 42 | SysmonLog 43 | tlntsvr 44 | w3wp 45 | WFDBG 46 | winget 47 | Winlogon 48 | Winmgmt 49 | winver 50 | Winword 51 | WLIDSVC 52 | xattr 53 | -------------------------------------------------------------------------------- /.vscode/cspell/psdocs/dictionaries/fictionalCorps.txt: -------------------------------------------------------------------------------- 1 | Adatum 2 | Contoso 3 | Fabrikam 4 | Humongous 5 | Lamna 6 | Lucerne 7 | Margie 8 | Munson 9 | Northwind 10 | Proseware 11 | Relecloud 12 | Southridge 13 | Tailspin 14 | Tailwind 15 | Trey 16 | VanArsdel 17 | Wingtip 18 | Woodgrove 19 | -------------------------------------------------------------------------------- /.vscode/cspell/psdocs/dictionaries/fileExtensions.txt: -------------------------------------------------------------------------------- 1 | adml 2 | admx 3 | asmx 4 | bmil 5 | cdxml 6 | dylib 7 | evtx 8 | maml 9 | mogg 10 | msix 11 | -------------------------------------------------------------------------------- /.vscode/cspell/psdocs/dictionaries/psdocs.txt: -------------------------------------------------------------------------------- 1 | ADSI 2 | AMSI 3 | Antimalware 4 | assetid 5 | Authenticode 6 | autoclose 7 | autocloses 8 | autoclosing 9 | autodetect 10 | autogenerate 11 | autogenerated 12 | autogenerates 13 | autoload 14 | autoloaded 15 | autoloads 16 | autosave 17 | autosaved 18 | autosaves 19 | backgrounded 20 | backtick 21 | backticks 22 | bareword 23 | barewords 24 | bigendianunicode 25 | blockquote 26 | blockquotes 27 | blog 28 | bootstrapped 29 | bootstrapper 30 | bootstrapping 31 | bootstraps 32 | bytecode 33 | callout 34 | callouts 35 | callstack 36 | carmonm 37 | ccontains 38 | CentOS 39 | cheatsheet 40 | cheatsheets 41 | checkboxes 42 | cimv2 43 | clike 44 | cmatch 45 | cmdlet 46 | cmdlets 47 | cnotcontains 48 | cnotlike 49 | cnotmatch 50 | codepage 51 | codepages 52 | computername 53 | coreclr 54 | CoreOS 55 | CredSSP 56 | creplace 57 | customizations 58 | DACL 59 | datacenter 60 | datacenters 61 | Debian 62 | deserialization 63 | deserialize 64 | deserialized 65 | deserializes 66 | devlang 67 | DHCP 68 | differentiators 69 | discoverability 70 | DISM 71 | doc-a-thon 72 | doc-a-thons 73 | docfx 74 | docset 75 | documentationcenter 76 | DPAPI 77 | DWORD 78 | endianness 79 | Etag 80 | Etags 81 | eventlog 82 | eventlogs 83 | executables 84 | façade 85 | failover 86 | finalizer 87 | frontmatter 88 | glob 89 | globbing 90 | globs 91 | GUID 92 | hardlink 93 | hashtable 94 | hashtables 95 | hasthable 96 | helpdesk 97 | HKEY 98 | hostname 99 | hostnames 100 | hotfix 101 | hotfixes 102 | HTTPS 103 | icontains 104 | ilike 105 | imatch 106 | inetpub 107 | infographic 108 | inotcontains 109 | inotlike 110 | inotmatch 111 | IntelliSense 112 | interpreted 113 | intranet 114 | intrinsics 115 | ireplace 116 | isnot 117 | JavaScript 118 | Kerberos 119 | keypress 120 | keypresses 121 | Kubernetes 122 | LASTEXITCODE 123 | LCID 124 | lifecycle 125 | lockdown 126 | markdig 127 | msiexec 128 | MSIL 129 | MSRC 130 | multibyte 131 | multithreading 132 | NETBIOS 133 | netstandard 134 | Newtonsoft 135 | Newtonsoft's 136 | NoBom 137 | notcontains 138 | notlike 139 | notmatch 140 | nslookup 141 | NTFS 142 | NTLM 143 | nuget 144 | nupkg 145 | onboarding 146 | openpublishing 147 | openSUSE 148 | pageable 149 | parameterless 150 | PATHEXT 151 | pltfrm 152 | POSIX 153 | PowerShell 154 | POWERSHELL_TELEMETRY_OPTOUT 155 | preinstallation 156 | prepopulate 157 | prepopulated 158 | prepopulates 159 | prepopulating 160 | prerelease 161 | prereleases 162 | psadapted 163 | psbase 164 | pscustomobject 165 | PSES 166 | psextended 167 | PSHOME 168 | PSHOST 169 | PSISE 170 | psobject 171 | PSRP 172 | PSSA 173 | pstypenames 174 | PSUICulture 175 | PSWSMan 176 | Punycode 177 | pwsh 178 | quickstartq 179 | quickstarts 180 | QWORD 181 | Raspbian 182 | Recurse 183 | recurses 184 | rehost 185 | rehosting 186 | rehosts 187 | rehydrated 188 | remoting 189 | reparse 190 | RHEL 191 | RIPEMD160 192 | runbook 193 | runbooks 194 | runspace 195 | runspaces 196 | runtimes 197 | SACL 198 | SBOM 199 | SBOMs 200 | sbyte 201 | scalability 202 | scanability 203 | SCCM 204 | scriptable 205 | scriptblocks 206 | scripter 207 | scripters 208 | Sddl 209 | SDII 210 | SDKs 211 | sdwheeler 212 | sewhee 213 | SIEM 214 | SLES 215 | Snapin 216 | Snapins 217 | Snover 218 | SPACEBAR 219 | Sqlcmd 220 | SQLPS 221 | stepover 222 | struct 223 | subcontainer 224 | subcontainers 225 | subexpression 226 | subexpressions 227 | subfolder 228 | subfolder 229 | subfolders 230 | subkey 231 | subkeys 232 | subnet 233 | subnets 234 | subpipeline 235 | subpipelines 236 | subproperties 237 | subproperty 238 | symlink 239 | systemdrive 240 | taskbar 241 | timespan 242 | Titlecase 243 | TMPDIR 244 | triaged 245 | Ubuntu 246 | UMCI 247 | undelimited 248 | unencrypted 249 | uninstallation 250 | unjoin 251 | unjoined 252 | unjoining 253 | unjoins 254 | unlist 255 | unlisted 256 | unlisting 257 | unlists 258 | unlocalized 259 | unmanaged 260 | unregister 261 | unregistered 262 | unregistering 263 | unregisters 264 | untrusted 265 | updateable 266 | USERMODE 267 | USERROLE 268 | userspace 269 | virtualized 270 | virtualizes 271 | walkthrough 272 | WBEM 273 | WDAC 274 | webservice 275 | WinCompat 276 | wmicimv2 277 | workgroup 278 | workgroups 279 | wwwroot 280 | -------------------------------------------------------------------------------- /.vscode/cspell/psdocs/dictionaries/pwshAliases.txt: -------------------------------------------------------------------------------- 1 | cd 2 | cdd 3 | chdir 4 | clc 5 | clhy 6 | cli 7 | clp 8 | cls 9 | clv 10 | cnsn 11 | cp 12 | cpi 13 | cpp 14 | curi 15 | cvpa 16 | hfid 17 | infa 18 | ipmo 19 | psedit 20 | ruri 21 | sasv 22 | spps 23 | spsv 24 | usetx 25 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | "docsmsft.docs-authoring-pack", 6 | "marvhen.reflow-markdown", 7 | "ms-vscode.powershell", 8 | "shuworks.vscode-table-formatter", 9 | "streetsidesoftware.code-spell-checker", 10 | "wmaurer.change-case", 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /Docs/Becoming-a-contributor.md: -------------------------------------------------------------------------------- 1 | # Becoming a contributor 2 | 3 | ## Prerequisites 4 | 5 | Before your post can be published, you need to have a WordPress account. Use the following steps to 6 | create an account. 7 | 8 | 1. Open [https://devblogs.microsoft.com/powershell-community][02] 9 | 1. Click the Login button in the top-right corner of the page. 10 | 1. Login using one of the three options 11 | 12 | ![blog login][01] 13 | 14 | 1. Fill in the following information in your profile: 15 | - First and Last name 16 | - The Display name should be your full name 17 | - Add any social media links you wish to share (optional) 18 | - Add a brief Bio explaining who you are / what your PowerShell experience is 19 | - Add a profile picture (optional) 20 | 21 | By default, your account is added as a **Subscriber** in WordPress. We will review your profile 22 | before you can be elevated to **Author** status. Your WordPress account must have **Author** 23 | permission before your post can be published. 24 | 25 | ## Getting started with GitHub and VS Code 26 | 27 | If you need help getting started with GitHub or the VS Code for markdown authoring, see the 28 | _Set up and work locally_ section of the public 29 | [Contributor's Guide][03]. 30 | 31 | This guide includes steps for the following items: 32 | 33 | 1. [Creating a GitHub account][03] 34 | 1. [Installing Git, VS Code, and the Docs Authoring Pack][05] 35 | 1. [Setting up your local Git repository][04] 36 | 1. [Git and GitHub fundamentals][06] 37 | 1. [An explanation of the full GitHub workflow][07] 38 | 39 | We also recommend installing the [posh-git][08] module from the PowerShell Gallery. This module makes 40 | it easier to use Git from the PowerShell command line. 41 | 42 | 43 | [01]: ./media/Becoming-a-contributor/blog-login.png 44 | [02]: https://devblogs.microsoft.com/powershell-community 45 | [03]: https://docs.microsoft.com/contribute/get-started-setup-github 46 | [04]: https://docs.microsoft.com/contribute/get-started-setup-local 47 | [05]: https://docs.microsoft.com/contribute/get-started-setup-tools 48 | [06]: https://docs.microsoft.com/contribute/git-github-fundamentals 49 | [07]: https://docs.microsoft.com/contribute/how-to-write-workflows-major 50 | [08]: https://www.powershellgallery.com/packages/posh-git 51 | -------------------------------------------------------------------------------- /Docs/Creating-a-new-post.md: -------------------------------------------------------------------------------- 1 | # Creating a new post 2 | 3 | 1. Always create a _working branch_ in your local repo before starting a new article. Avoid working 4 | in the `main` branch. 5 | 1. Create a new `.md` file in `Posts/YYYY/MM` directory. For example, posts scheduled to be 6 | published in February of 2021 go in the `Posts/2021/02` folder. Create the monthly folder if it 7 | doesn't exist yet. 8 | - Filenames should only use the following characters: A-Z (upper and lower), 0-9, and hyphen (`-`) 9 | - Don't use spaces or special characters in filenames 10 | - Separate words in the filename with hyphens 11 | - The filename must include the `.md` file extension 12 | 1. Write the blog post! 13 | - Use [GitHub flavored markdown][1]. 14 | - The blog post **MUST** have this header: 15 | 16 | ```yaml 17 | --- 18 | post_title: 'Post Title' 19 | user_login: The author's Word Press username, not GitHub ID 20 | author1: 21 | author2: The author's Word ress username, not GitHub ID 22 | author3: The author's Word Press username, not GitHub ID 23 | post_slug: 24 | categories: existingcategory1, existingcategory2 25 | tags: tag1, tag2 26 | summary: summary of the post 27 | --- 28 | 29 | Add your blog post here 30 | ``` 31 | 32 | - The `post_title`, `summary`, and `user_login` are required fields. 33 | - `post_slug` is an identifier for your post and it becomes the end portion of the URL for the 34 | post. 35 | - If the slug exists in Word Press, the post matching the slug is updated. 36 | - If the slug does not exist in Word Press, a new post is created. 37 | - If you don't provide a slug, Word Press creates a slug when it creates the draft. 38 | - Use lowercase letters, numbers, and hyphens. Use hyphens to separate words rather than other 39 | punctuation. 40 | - For more information about slugs, see 41 | [What is a Word Press slug?](https://www.wpkube.com/wordpress-slug/) 42 | - `categories` - one or more category strings separated by commas 43 | - The category values must already exist in your blog 44 | - `tags` - one or more strings separated by commas 45 | - New values are added as available tags for your blog in Word Press 46 | - `summary` - This is the short description of the post that shows in listing of posts on the 47 | main page of your blog 48 | - `user_login` or `author1` - can be used to add a single author 49 | - `author2` or `author3` - should be used when adding up to two additional authors 50 | 51 | - PowerShell code snippet: 52 | 53 | ~~~markdown 54 | ```powershell 55 | Get-Alias dir # this will be highlighted with PowerShell syntax 56 | ``` 57 | ~~~ 58 | 59 | - Console output snippet: 60 | 61 | ~~~markdown 62 | ```powershell-console 63 | CommandType Name Version Source 64 | ----------- ---- ------- ------ 65 | Alias dir -> Get-ChildItem 66 | ``` 67 | ~~~ 68 | 69 | 1. Read and follow the rules in the [Reviewer's Guide][2]. Edit your post based on these rules 70 | before submitting the PR. This saves the reviewers a lot of time and your post can be approved 71 | more quickly. 72 | 73 | ## Publishing draft to blog 74 | 75 | After submitting your Pull Request, the blog admins will review the post. They may suggest editorial 76 | changes to improve grammar and readability. They may also require specific changes before we can 77 | publish. Once the pull request is merged, the post is automatically copied to Word Press as a draft. 78 | From there, the Blog admins will verify that the post renders correctly, make any formatting changes 79 | required, and publish the post. 80 | 81 | 82 | [1]: ./Markdown-cheatsheet.md 83 | [2]: ./Reviewers-Guide.md 84 | -------------------------------------------------------------------------------- /Docs/GitHub-workflow-for-new-post.md: -------------------------------------------------------------------------------- 1 | # GitHub workflow for a new post 2 | 3 | The following image illustrates the workflow for using Git and GitHub to create a new post for the 4 | Community blog. The steps shown in red are a one-time action and are covered in Setup GitHub for 5 | [local workflow][1]. The numbered steps (in black) are described in the table below. 6 | 7 | ![Blog GitHub workflow][2] 8 | 9 | | Steps | Description of steps | Git command / GitHub actions | | 10 | | ----- | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --- | 11 | | 0 | Checkout the main branch | `git checkout main` | | 12 | | 1 | Sync the main branch | `git pull upstream main`
`git push origin main` | | 13 | | 2 | Create a new working branch | git checkout -b new-post-name | | 14 | | 3 | Create new content | Use VS Code to create new blog post | | 15 | | 4-5 | Commit changes to local repo | `git add -A`
`git commit -m 'commit message'` | | 16 | | 6 | Push working branch to fork | git push origin new-post-name | | 17 | | 7 | Submit pull request | Go to `https://github.com//Community-Blog/pulls` and click the **New pull request** button.

`Base repository: PowerShell/Community-Blog base: main <-- head repository: /Community-Blog compare: new-post-name`

Fill out the pull request description and click **Submit**. | | 18 | | 8 | PR is reviewed | Make the necessary changes based on the review feedback. | | 19 | | 9 | PR is merged | Go to step 10 | | 20 | | 10 | Cleanup unneeded branch info | `git checkout main`
`git push origin --delete new-post-name`
`git branch -D new-post-name`

The `git push` command deletes the branch in your fork and deletes the tracking branch from your local repo. The `git branch` command delete the branch from your local repo. | | 21 | | 11 | Start new post | Go to step 0 | . | 22 | 23 | 24 | [1]: ./Setup-GitHub-for-Local-Workflow.md 25 | [2]: ./media/GitHub-workflow-for-new-post/Blog-gitflow.png 26 | -------------------------------------------------------------------------------- /Docs/Ideas-for-Posts.md: -------------------------------------------------------------------------------- 1 | # Ideas for a new post 2 | 3 | ## Share your experiences 4 | 5 | Ideas for posts can come from anywhere, but the best posts come from your own experience. Think 6 | about a time when you solved a unique problem using PowerShell or learned something new about 7 | PowerShell. Personal stories make good content. 8 | 9 | Blog posts should be your own original content. It's acceptable to reuse content you posted to 10 | another blog as long as you own the rights to reuse that content. 11 | 12 | ## Update a post from the Scripting Blog 13 | 14 | There is a lot of useful content on the old [Scripting Blog][1], but the information may be 15 | incomplete, no longer accurate, or needs to be updated for the current version of PowerShell. 16 | 17 | If you find a post on the old blog that you think could be updated, create a new issue using the 18 | [Request update to old post from The Scripting Blog][2] issue template. Include the following 19 | information: 20 | 21 | - Link to the post in the old blog 22 | - Description of what needs to be changed 23 | 24 | Then you can start working on a rewrite or you wait for someone else to create an update. 25 | 26 | ## Look for issues labeled `up-for-grabs` 27 | 28 | Anyone can submit ideas for new posts or requests for updates to existing posts, but not everyone is 29 | ready to write that post. Issues that are labeled `up-for-grabs` are open to anyone who might be 30 | interested in writing that post. You can get a complete list of these issues by 31 | [filtering the view][3] on that label. 32 | 33 | ## Acceptable content ideas 34 | 35 | The intended purpose of this blog is to provide a blogging platform for the PowerShell Community, 36 | both internal and external to Microsoft's PowerShell team. Posts to the blog can discuss products 37 | and technologies that aren't part of the core PowerShell product or even made by Microsoft, as long 38 | as the content is relevant to PowerShell users and isn't marketing those products. 39 | 40 | Acceptance of any blog post is done at the sole discretion of the Blog admins. 41 | 42 | Acceptable posts meet one or more of the following criteria: 43 | 44 | - Show how to use PowerShell to solve a specific problem or scenario 45 | - Explain PowerShell usage in more detail than provided in the documentation 46 | - Doesn't contain marketing materials or product announcements 47 | - Examples using non-Microsoft products and modules are allowed as long as the purpose is to 48 | demonstrate a solution 49 | 50 | 51 | [1]: https://devblogs.microsoft.com/scripting 52 | [2]: https://github.com/PowerShell/Community-Blog/issues/new?assignees=&labels=post-update&template=Request_Update.md&title=Update+request 53 | [3]: https://github.com/PowerShell/Community-Blog/issues?q=is%3Aissue+is%3Aopen+label%3Aup-for-grabs 54 | -------------------------------------------------------------------------------- /Docs/README.md: -------------------------------------------------------------------------------- 1 | # Welcome to the Community-Blog contributor guide 2 | 3 | The Wiki contains documentation on how to create a new blog post and how we operate this blog. 4 | 5 | Participation in this blog is governed by the [Microsoft Open Source Code of Conduct][1] or the 6 | [.NET Foundation Code of Conduct][2]. For more information, see the [Code of Conduct FAQ][3]. 7 | 8 | Making contributions to the blog is very similar to making contributions to docs.microsoft.com. To 9 | get started, you require the following things: 10 | 11 | 1. A WordPress account (see [Becoming a contributor][4]) 12 | 1. A GitHub account 13 | 1. Familiarity with Markdown and VS Code 14 | 1. Familiarity with the GitHub workflow 15 | 16 | If you need help getting started with GitHub or the Docs authoring process, see the _Set up and work 17 | locally_ section of the public [Contributor's Guide][5]. 18 | 19 | Formatting of the content should follow the same rules published in the 20 | [PowerShell-specific Contributors Guide][6]. and the [PowerShell Style Guide][7]. 21 | 22 | ## Table of Contents 23 | 24 | - [1 - Becoming a contributor][8] 25 | - [2 - Creating a new-post][9] 26 | - [3 - Submitting a PR][10] 27 | 28 | ### Appendices 29 | 30 | - [A - Markdown cheatsheet][11] 31 | - [B - Setup GitHub for local workflow][12] 32 | - [C - GitHub workflow for a new post][13] 33 | - [D - Ideas for Posts][14] 34 | 35 | ## Operations Guide 36 | 37 | - [Reviewer's Guide][15] 38 | 39 | 40 | [1]: https://opensource.microsoft.com/codeofconduct/ 41 | [2]: https://dotnetfoundation.org/code-of-conduct 42 | [3]: https://opensource.microsoft.com/codeofconduct/faq/ 43 | [4]: Becoming-a-contributor 44 | [5]: https://docs.microsoft.com/contribute/get-started-setup-github 45 | [6]: https://docs.microsoft.com/powershell/scripting/community/contributing/overview 46 | [7]: https://docs.microsoft.com/powershell/scripting/community/contributing/powershell-style-guide 47 | [8]: Becoming-a-contributor.md 48 | [9]: Creating-a-new-post.md 49 | [10]: Submitting-a-PR.md 50 | [11]: Markdown-cheatsheet.md 51 | [12]: Setup-GitHub-for-Local-Workflow.md 52 | [13]: GitHub-workflow-for-new-post.md 53 | [14]: Ideas-for-Posts.md 54 | [15]: Reviewers-Guide.md 55 | -------------------------------------------------------------------------------- /Docs/Reviewers-Guide.md: -------------------------------------------------------------------------------- 1 | # Reviewer's Guide 2 | 3 | This is a summary of rules to apply when writing new or updating existing articles. See other 4 | articles in the Contributor's Guide for detailed explanations and examples of these rules. 5 | 6 | ## PR Quality 7 | 8 | - Submitted from a working branch (not from main) 9 | - PR contains only one post 10 | - The post is in the correct folder with a correctly structured filename 11 | `Posts/yyyy/mm/simple-title-of-post.md` 12 | - No spaces in filenames 13 | - Lowercase preferred 14 | - Must have `.md` file extension 15 | 16 | ## User profile 17 | 18 | - The submitter has a valid WordPress profile with appropriate personal information 19 | - Full name 20 | - Any links included are valid and appropriate 21 | - The picture is appropriate (avatars are OK - prefer actual photo) 22 | - The submitter has been changed to the **Author** role in WordPress 23 | 24 | ## Content quality 25 | 26 | - Reasonable grammar and spelling 27 | - Correct spelling and usage of brands (eg. "PowerShell" not "Powershell") 28 | - Content is designed to teach or inform not market or sell 29 | - Submitter has proper rights to the content they are submitting 30 | 31 | ## Metadata 32 | 33 | All blog posts must include the YAML frontmatter: 34 | 35 | ```yaml 36 | --- 37 | post_title: 38 | username: <Author username as seen in wordpress, not github ID> 39 | categories: <choose from list of predefined categories> 40 | tags: <choose from list of predefined tags> 41 | featured_image: <Optional Image url> 42 | summary: <Summary of the post - short one-line description> 43 | --- 44 | ``` 45 | 46 | ## Formatting 47 | 48 | - Backtick syntax elements that appear, inline, within a paragraph 49 | - Cmdlet names `Verb-Noun` 50 | - Variable `$counter` 51 | - Syntactic examples `Verb-Noun -Parameter` 52 | - File paths `C:\Program Files\PowerShell`, `/usr/bin/pwsh` 53 | - URLs that aren't meant to be clickable in the document 54 | - Property or parameter values 55 | - Use bold for property names, parameter names, class names, module names, entity names, object or 56 | type names 57 | - Bold is used for semantic markup, not emphasis 58 | - Bold - use asterisks `**` 59 | - Italic - use underscore `_` 60 | - Only used for emphasis, not for semantic markup 61 | - Line breaks at 100 columns - helps when reviewing diffs 62 | - Use the [Reflow Markdown][1] extension in VS Code to help 63 | - No hard tabs - use spaces only 64 | - No trailing spaces on lines 65 | 66 | ### Headers 67 | 68 | - DO NOT use the H1 header - WordPress automatically puts the title at the top of the post 69 | - Use [ATX Headers][2] only 70 | - Use sentence case for all headers 71 | - Don't skip levels - no H3 without an H2 72 | - Max depth of H3 or H4 73 | - Blank line before and after 74 | 75 | ### Code blocks 76 | 77 | - Blank line before and after the code block (not inside the code block) 78 | - Use tagged code fences - `powershell`, `powershell-console`, or other appropriate language tags 79 | - Untagged fence - syntax blocks or other shells 80 | - Put output in separate `powershell-console` code block 81 | - Don't use the PowerShell prompt in code blocks unless: 82 | - You are showing an example meant to be used on the command line. 83 | - Use `PS>` for the prompt unless the path is important to the example. 84 | 85 | ### Lists 86 | 87 | - Blank line before first item and after last item 88 | - Indented properly 89 | - Additional lines for an item should line up with first character after the list marker 90 | - Bullet - use hyphen (`-`) not asterisk (`*`) - too easy to confuse with emphasis 91 | - For numbered lists, all numbers are "1." 92 | 93 | ## Terminology 94 | 95 | - PowerShell vs. Windows PowerShell vs. PowerShell Core 96 | - See [Product Terminology][3] 97 | 98 | ## Linking to other websites 99 | 100 | - Do not include locales in URLs linking to Microsoft properties (eg. remove `/en-us` from URL) 101 | - Do not include the `?view=<version>` query parameter when linking to docs.microsoft.com 102 | - All URLs to websites should use HTTPS unless that is not valid for the target site 103 | - Image links should have unique alt text 104 | - No bare URLs - Use standard markdown link syntax `[text of link](https://site.domain/path/to/page#anchor)` 105 | - The link text should be the title of the page or the anchor that you link to 106 | 107 | <!-- link references --> 108 | [1]: https://marketplace.visualstudio.com/items?itemName=marvhen.reflow-markdown 109 | [2]: https://github.github.com/gfm/#atx-headings 110 | [3]: https://learn.microsoft.com/powershell/scripting/community/contributing/product-terminology 111 | -------------------------------------------------------------------------------- /Docs/Setup-GitHub-for-Local-Workflow.md: -------------------------------------------------------------------------------- 1 | # Setting up Git/GitHub for working locally 2 | 3 | To contribute to the blog, you can make and edit Markdown files locally by cloning the corresponding 4 | **PowerShell/Community-Blog** repository. Microsoft requires you to fork the repository into your 5 | own GitHub account so that you have read/write permissions there to store your proposed changes. 6 | Then you use pull requests to merge changes into the read-only central shared repository. 7 | 8 | ![GitHub Triangle][1] 9 | 10 | ## Fork the repository 11 | 12 | Create a fork of the **PowerShell/Community-Blog** repository into your own GitHub account using 13 | the GitHub website. 14 | 15 | A personal fork is required since the main repositories provide read-only access. To make changes, 16 | you must submit a [pull request][2] from your fork into the main repository. To facilitate this 17 | process, you first need your own copy of the repository. A GitHub _fork_ serves that purpose. 18 | 19 | 1. Go to [https://github.com/PowerShell/Community-Blog][3] 20 | and click the **Fork** button on the upper right. 21 | 22 | ![GitHub profile example][4] 23 | 24 | 2. If you are prompted, select your GitHub account tile as the destination where the fork should be 25 | created. This prompt creates a copy of the repository within your GitHub account, known as a 26 | fork. 27 | 28 | ## Choose a local folder 29 | 30 | Make a local folder to hold a copy of the repository locally. Some repositories can be large; up to 31 | 5 GB for Community-Blog for example. Choose a location with available disk space. 32 | 33 | 1. Choose a foldername should be easy for you to remember and type. For example, consider a root 34 | folder `C:\Git\` or make a folder in your user profile directory `~/Documents/Git/` 35 | 36 | > [!IMPORTANT] 37 | > Avoid choosing a local folder path that's nested inside of another git repository folder 38 | > location. While it's acceptable to store the git cloned folders adjacent to each other, nesting 39 | > git folders inside one another causes errors for the file tracking. 40 | 41 | 1. From the PowerShell command line, change directory (`cd`) into the folder that you created for 42 | hosting the repository locally. Note that Git Bash uses the Linux convention of forward-slashes 43 | instead of back-slashes for folder paths. 44 | 45 | For example, `cd ~/Documents/Git/` 46 | 47 | ## Create a local clone 48 | 49 | Prepare to run the **clone** command to pull a copy of a repository (your fork) down to your device 50 | on the current directory. 51 | 52 | ### Authenticate using Git Credential Manager 53 | 54 | If you installed the latest version of Git for Windows and accepted the default installation, Git 55 | Credential Manager is enabled by default. Git Credential Manager makes authentication much easier 56 | because you don't need to recall your personal access token when re-establishing authenticated 57 | connections and remotes with GitHub. 58 | 59 | 1. Run the **clone** command, by providing the repository name. Cloning downloads (clone) the forked 60 | repository on your local computer. 61 | 62 | > [!Tip] 63 | > You can get your fork's GitHub URL for the clone command from the **Clone or download** button 64 | > in the GitHub UI: 65 | > 66 | > ![Clone or download][5] 67 | 68 | Be sure to specify the path to *your fork* during the cloning process, not the main repository 69 | from which you created the fork. Otherwise, you cannot contribute changes. Your fork is 70 | referenced through your personal GitHub user account, such as 71 | `github.com/<github-username>/<repo>`. 72 | 73 | ```powershell 74 | git clone https://github.com/<github-username>/<repo>.git 75 | ``` 76 | 77 | Your clone command should look similar to this example: 78 | 79 | ```powershell 80 | git clone https://github.com/MyGitAccount/Community-Blog.git 81 | ``` 82 | 83 | 1. When you're prompted, enter your GitHub credentials. 84 | 85 | ![GitHub Login][6] 86 | 87 | 1. When you're prompted, enter your two-factor authentication code. 88 | 89 | ![GitHub two-factor authentication][7] 90 | 91 | > [!NOTE] 92 | > Your credentials are saved and used to authenticate future GitHub requests. You only need to 93 | > do this authentication once per computer. 94 | 95 | 1. The clone command downloads a copy of the files from your fork of the repository into a new 96 | folder on the local disk. The new folder is created within the current folder. It may take a few 97 | minutes, depending on the repository size. You can explore the folder to see the structure once 98 | it is finished. 99 | 100 | ## Configure remote upstream 101 | 102 | After cloning the repository, set up a read-only remote connection to the main repository named 103 | **upstream**. You use the upstream URL to keep your local repository in sync with the latest changes 104 | made by others. The **git remote** command is used to set the configuration value. You use the 105 | **fetch** command to refresh the branch info from the upstream repository. 106 | 107 | 1. Use the following commands. 108 | 109 | ```powershell 110 | cd Community-Blog 111 | git remote add upstream https://github.com/PowerShell/Community-Blog.git 112 | git fetch upstream 113 | ``` 114 | 115 | 1. View the configured values and confirm the URLs are correct. Ensure the **origin** URLs point to 116 | your personal fork. Ensure the **upstream** URLs point to the main repository, such as 117 | MicrosoftDocs or Azure. 118 | 119 | ```powershell 120 | git remote -v 121 | ``` 122 | 123 | Example remote output is shown. A fictitious git account named MyGitAccount is configured with a 124 | personal access token to access the repo Community-Blog: 125 | 126 | ```output 127 | origin https://github.com/MyGitAccount/Community-Blog.git (fetch) 128 | origin https://github.com/MyGitAccount/Community-Blog.git(push) 129 | upstream https://github.com/PowerShell/Community-Blog.git (fetch) 130 | upstream https://github.com/PowerShell/Community-Blog.git (push) 131 | ``` 132 | 133 | 1. If you made a mistake, you can remove the remote value. To remove the upstream value, run the 134 | command `git remote remove upstream`. 135 | 136 | <!-- link references --> 137 | [1]: ./media/Setup-GitHub-for-Local-Workflow/git-and-github-initial-setup.png 138 | [2]: https://docs.microsoft.com/contribute/git-github-fundamentals 139 | [3]: https://github.com/PowerShell/Community-Blog 140 | [4]: ./media/Setup-GitHub-for-Local-Workflow/fork.png 141 | [5]: ./media/Setup-GitHub-for-Local-Workflow/clone-or-download.png 142 | [6]: ./media/Setup-GitHub-for-Local-Workflow/github-login.png 143 | [7]: ./media/Setup-GitHub-for-Local-Workflow/github-2fa.png 144 | -------------------------------------------------------------------------------- /Docs/Submitting-a-PR.md: -------------------------------------------------------------------------------- 1 | # Submitting a Pull Request (PR) 2 | 3 | 1. Create your a new MD file for your post 4 | 5 | - Always create a _working branch_ in your local repo before starting a new article. Avoid 6 | working in the `main` branch. 7 | - Create the new post in the folder for the current month (E.g. `Posts/2021/02` for February of 8 | 2021). 9 | - All images you want to include go in the `media` folder of the current month. 10 | - If the media folder doesn't exist, create it. 11 | - Create a subfolder that matches the name of you post's MD file in the `media` folder. 12 | 13 | 1. Write your content in markdown. 14 | 15 | - Be sure to include the YAML frontmatter in your file. 16 | - Follow the guidance in our [Reviewer's Guide][1]. 17 | - You don't need to repeat the title as an H1 header. The first header in your post should be 18 | H2. This header DOES NOT need to be the first lin of you post after the frontmatter. 19 | 20 | 1. Push your _working branch_ to your fork in GitHub. 21 | 1. Create a PR to merge the _working branch_ of your fork into the `main` branch of the 22 | **PowerShell/Community-Blog** repository. 23 | 1. Fill out the PR template and click submit. 24 | 25 | - Include specific instructions in the template if you want the post to be published at a 26 | specific date and time. 27 | 28 | ## After submitting your PR 29 | 30 | ### Contributor License Agreement 31 | 32 | If you are contributing for the first time, you will be asked to complete a short Contribution 33 | License Agreement (CLA). After the CLA step is cleared, your pull request is processed. 34 | 35 | ### PR Review process 36 | 37 | Your PR will be reviewed by the Blog staff. They may have editorial suggestions. Please fix issues 38 | and accept the editorial changes. 39 | 40 | Once your PR has been reviewed and approved, it will be merged. Once merged the post is 41 | automatically copied to WordPress where the WP admins will preview the rendering. They may have to 42 | make minor changes to fix issues created by the translation to WordPress. If the Preview is good, 43 | the post will be published. 44 | 45 | <!-- link references --> 46 | [1]: https://github.com/PowerShell/Community-Blog/wiki/Reviewers-Guide 47 | -------------------------------------------------------------------------------- /Docs/media/Becoming-a-contributor/blog-login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PowerShell/Community-Blog/7837d320a6bd85ca7d4469dcfaa115112333fac0/Docs/media/Becoming-a-contributor/blog-login.png -------------------------------------------------------------------------------- /Docs/media/GitHub-workflow-for-new-post/Blog-gitflow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PowerShell/Community-Blog/7837d320a6bd85ca7d4469dcfaa115112333fac0/Docs/media/GitHub-workflow-for-new-post/Blog-gitflow.png -------------------------------------------------------------------------------- /Docs/media/Markdown-cheetsheet/alerts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PowerShell/Community-Blog/7837d320a6bd85ca7d4469dcfaa115112333fac0/Docs/media/Markdown-cheetsheet/alerts.png -------------------------------------------------------------------------------- /Docs/media/Setup-GitHub-for-Local-Workflow/clone-or-download.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PowerShell/Community-Blog/7837d320a6bd85ca7d4469dcfaa115112333fac0/Docs/media/Setup-GitHub-for-Local-Workflow/clone-or-download.png -------------------------------------------------------------------------------- /Docs/media/Setup-GitHub-for-Local-Workflow/edit-article.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PowerShell/Community-Blog/7837d320a6bd85ca7d4469dcfaa115112333fac0/Docs/media/Setup-GitHub-for-Local-Workflow/edit-article.png -------------------------------------------------------------------------------- /Docs/media/Setup-GitHub-for-Local-Workflow/fork.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PowerShell/Community-Blog/7837d320a6bd85ca7d4469dcfaa115112333fac0/Docs/media/Setup-GitHub-for-Local-Workflow/fork.png -------------------------------------------------------------------------------- /Docs/media/Setup-GitHub-for-Local-Workflow/git-and-github-initial-setup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PowerShell/Community-Blog/7837d320a6bd85ca7d4469dcfaa115112333fac0/Docs/media/Setup-GitHub-for-Local-Workflow/git-and-github-initial-setup.png -------------------------------------------------------------------------------- /Docs/media/Setup-GitHub-for-Local-Workflow/gitbash-start.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PowerShell/Community-Blog/7837d320a6bd85ca7d4469dcfaa115112333fac0/Docs/media/Setup-GitHub-for-Local-Workflow/gitbash-start.png -------------------------------------------------------------------------------- /Docs/media/Setup-GitHub-for-Local-Workflow/github-2fa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PowerShell/Community-Blog/7837d320a6bd85ca7d4469dcfaa115112333fac0/Docs/media/Setup-GitHub-for-Local-Workflow/github-2fa.png -------------------------------------------------------------------------------- /Docs/media/Setup-GitHub-for-Local-Workflow/github-login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PowerShell/Community-Blog/7837d320a6bd85ca7d4469dcfaa115112333fac0/Docs/media/Setup-GitHub-for-Local-Workflow/github-login.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 PowerShell Team 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Posts/2021/02/Announcing-Community-Blog.md: -------------------------------------------------------------------------------- 1 | --- 2 | post_title: Announcing the PowerShell Community Blog 3 | username: sewhee@microsoft.com 4 | categories: PowerShell 5 | tags: Blog news, Announcement 6 | featured_image: 7 | summary: 8 | --- 9 | 10 | # Announcing the PowerShell Community Blog 11 | 12 | We are proud to announce the a new blog dedicated to PowerShell and focused on the community. This 13 | is a blog by the community and for the community. And we have made it easier than ever for you to 14 | contribute to the new blog. 15 | 16 | It all started with a generous [offer](https://twitter.com/doctordns/status/1343618958407168000) 17 | from a member of the community, Thomas Lee ([@doctordns](https://twitter.com/doctordns)). 18 | 19 | ![doctordns-tweet](./media/Announcing-Community-Blog/doctordns-tweet.png) 20 | 21 | We knew we had a great opportunity here. But, rather than fixing the content in the Scripting blog, 22 | what we really needed was a fresh start. So we decided to 23 | [retire the Scripting blog](https://devblogs.microsoft.com/scripting/all-good-things-must-come-to-an-end/) 24 | and create this new one. 25 | 26 | ## A blog for the community 27 | 28 | Like the **Scripting** blog, this new blog is focused on teaching you about PowerShell, answering 29 | your questions, and providing interesting and useful examples. We will continue to use the 30 | [scripter@microsoft.com](mailto:scripter@microsoft.com?subject=Community%20Blog%20question) mailbox 31 | to accept your questions. But there are new ways to engage with this blog. 32 | 33 | ## A blog by the community 34 | 35 | On the **Scripting** blog, it was possible for members of the community to submit blog posts to our 36 | email address. The Scripting Guys would review the offered content and publish the post for you. 37 | 38 | With the new blog we have a new way to contribute. All posts to this blog are open-source markdown 39 | files store in GitHub. You can share your own knowledge by submitting pull requests. And you can use 40 | the Issues system to ask question, point out problems, or suggest ideas for new posts. 41 | 42 | ## Getting started 43 | 44 | Thomas Lee already has several posts from the old Scripting blog that he has updated and corrected. 45 | Look for those posts here in the coming weeks. If you have other posts from the old blog that you 46 | would like to see updated, please open an issue in the 47 | [Community-Blog](https://github.com/PowerShell/Community-Blog/issues) repository. 48 | 49 | The content in the Scripting blog is not going away. But rather than updating the posts there, where 50 | they will be lost among the 5400+ existing posts, the updated post will be published here as fresh 51 | content that is easy to find. 52 | 53 | So now it's your turn. Check out this 54 | [page](https://devblogs.microsoft.com/powershell-community/how-to-contribute-to-the-blog/) to learn 55 | how to get started. 56 | -------------------------------------------------------------------------------- /Posts/2021/02/Coming-Soon.md: -------------------------------------------------------------------------------- 1 | --- 2 | post_title: Coming soon 3 | username: sewhee@microsoft.com 4 | categories: PowerShell 5 | tags: PowerShell 6 | featured_image: 7 | summary: Stay tuned for more exciting developments. 8 | --- 9 | # Coming Soon 10 | 11 | Stay tuned for more exciting developments. 12 | -------------------------------------------------------------------------------- /Posts/2021/02/Get-Services-PS7-VS-PS5.1.md: -------------------------------------------------------------------------------- 1 | --- 2 | post_title: Using Get-Service in PowerShell 7 vs. Windows PowerShell 5.1 3 | username: farisnt@gmail.com 4 | Catagories: PowerShell, Windows Services 5 | Summary: Show how to get services information using both Windows PowerShell 5.1 and PowerShell 7 6 | --- 7 | 8 | # Get-Services, PowerShell 7 VS Windows PowerShell 5.1 9 | 10 | **Q**: How can I get the Username, and StartType for a Windows Service? 11 | 12 | **A**: Quick answer is PowerShell 7. 13 | 14 | Microsoft is doing a great job on PowerShell with each version they release. The simple answer to this question is a command called `Get-Service`. But there is a big update that makes getting the required information much easier with PowerShell 7. I will show the result of this command using both **PowerShell 7** and **Windows PowerShell 5.1**. 15 | 16 | Let's start by typing the simple command `Get-Service Workstation`. This command return basic details for a service called **Workstation**. The result is the same for both PowerShell 7 and Windows PowerShell 5.1. 17 | 18 | ```powershell 19 | Status Name DisplayName 20 | ------ ---- ----------- 21 | Running LanmanWorkstation Workstation 22 | ``` 23 | 24 | To drill-down and get a more detailed result, we need to see all the associated _properties_ and _methods_ for this service, which can be achieved using the following command. 25 | 26 | ## Getting Windows Services using Get-Service 27 | 28 | ```powershell 29 | Get-Service Workstation | Get-Member | Select-Object Name, MemberType 30 | ``` 31 | 32 | The output returns a list of members that can be invoked in the command line. Here is the output in PowerShell 7. 33 | 34 | ```powershell 35 | Name MemberType 36 | ---- ---------- 37 | Name AliasProperty 38 | RequiredServices AliasProperty 39 | Disposed Event 40 | Close Method 41 | Continue Method 42 | Dispose Method 43 | Equals Method 44 | ExecuteCommand Method 45 | GetHashCode Method 46 | GetLifetimeService Method 47 | GetType Method 48 | InitializeLifetimeService Method 49 | Pause Method 50 | Refresh Method 51 | Start Method 52 | Stop Method 53 | WaitForStatus Method 54 | BinaryPathName Property 55 | CanPauseAndContinue Property 56 | CanShutdown Property 57 | CanStop Property 58 | Container Property 59 | DelayedAutoStart Property 60 | DependentServices Property 61 | Description Property 62 | DisplayName Property 63 | MachineName Property 64 | ServiceHandle Property 65 | ServiceName Property 66 | ServicesDependedOn Property 67 | ServiceType Property 68 | Site Property 69 | StartType Property 70 | StartupType Property 71 | Status Property 72 | UserName Property 73 | ToString ScriptMethod 74 | ``` 75 | 76 | Here is the output in Windows PowerShell 5.1. 77 | 78 | ```powershell 79 | Name MemberType 80 | ---- ---------- 81 | Name AliasProperty 82 | RequiredServices AliasProperty 83 | Disposed Event 84 | Close Method 85 | Continue Method 86 | CreateObjRef Method 87 | Dispose Method 88 | Equals Method 89 | ExecuteCommand Method 90 | GetHashCode Method 91 | GetLifetimeService Method 92 | GetType Method 93 | InitializeLifetimeService Method 94 | Pause Method 95 | Refresh Method 96 | Start Method 97 | Stop Method 98 | WaitForStatus Method 99 | CanPauseAndContinue Property 100 | CanShutdown Property 101 | CanStop Property 102 | Container Property 103 | DependentServices Property 104 | DisplayName Property 105 | MachineName Property 106 | ServiceHandle Property 107 | ServiceName Property 108 | ServicesDependedOn Property 109 | ServiceType Property 110 | Site Property 111 | StartType Property 112 | Status Property 113 | ToString ScriptMethod 114 | ``` 115 | 116 | ## PowerShell 7 117 | 118 | The big difference is in the **Property** members. Now, in PowerShell 7, it's possible to read some additional properties that were not available in Windows PowerShell 5.1, such as **UserName**, **BinaryPathName**, **StartType**. So let's see how to read these properties using PowerShell 7. 119 | 120 | ```powershell 121 | PS 7> Get-Service workstation | select Username,Starttype,BinaryPathName 122 | ``` 123 | 124 | The output is a clear, with all the required results using a native one-liner command. 125 | 126 | ```powershell 127 | UserName StartType BinaryPathName 128 | -------- --------- -------------- 129 | NT AUTHORITY\NetworkService Automatic C:\WINDOWS\System32\svchost.exe -k NetworkService -p 130 | ``` 131 | 132 | ## Windows Powershell 5.1 133 | 134 | For Windows PowerShell 5.1, the operation is not as simple as in PowerShell. We need to use the `Get-CimInstance` and pass the required WQL query. So, in Windows PowerShell 5.1, run the following command: 135 | 136 | ```powershell 137 | WPS 5.1> Get-CimInstance -Query 'select * from Win32_Service where caption like "Workstation"' | select StartName,StartMode,PathName 138 | ``` 139 | 140 | ```powershell 141 | StartName StartMode PathName 142 | --------- --------- -------- 143 | NT AUTHORITY\NetworkService Auto C:\WINDOWS\System32\svchost.exe -k NetworkService -p 144 | ``` 145 | 146 | In this example, we invoke `Get-CimInstance` with a query to get the service name and then select the required properties, which is a long way and requires you to know extra information related to the original service name and some basic WMI query language. 147 | 148 | ## Summary 149 | 150 | More and more to come with PowerShell 7, ease of use, backward compatibility much rich experience. This post shows a small portion of a small change in PowerShell which will help a lot of admin in their day-to-day tasks. 151 | -------------------------------------------------------------------------------- /Posts/2021/02/ScriptingGuy.0002.GetDateOfYesterday.md: -------------------------------------------------------------------------------- 1 | --- 2 | post_title: Getting Yesterday's Date 3 | username: tfl@psp.co.uk 4 | Catagories: PowerShell 5 | tags: .NET, Scripting Guys Update 6 | Summary: Shows how to get a recent date and use that in your scripting. 7 | --- 8 | 9 | 10 | **Q:** How can I get yesterday's date? 11 | 12 | **A:** You can use a combination of the `Get-Date` cmdlet and .NET Time/Date methods. 13 | 14 | First, let's look at dates in PowerShell and .NET 15 | Then we can look at how to calculate yesterday and use that in your scripts. 16 | 17 | ## Dates in PowerShell 18 | 19 | Let's start by looking at how you can deal with dates and times. 20 | As you probably know, PowerShell contains the `Get-Date` cmdlet. 21 | This cmdlet returns a .NET **System.DateTime** object. 22 | 23 | Using the `Get-Date` cmdlet, you can get any date and time, and either display it or store it in a variable. 24 | To get today's date. you could do this: 25 | 26 | ```powershell-console 27 | PS C:\> # Get the current date 28 | PS C:\> Get-Date 29 | 08 January 2021 11:24:46 30 | 31 | # Store the date in a variable 32 | $Now = Get-Date 33 | $Now 34 | 08 January 2021 11:24:47 35 | ``` 36 | 37 | As mentioned, the `Get-Date` cmdlet returns an object whose type is **System.DateTime**. 38 | This .NET structure provides a rich set of properties and methods to help you manipulate the date/time object. 39 | See the [System.DateTime documentation](https://docs.microsoft.com/dotnet/api/system.datetime) for more details on this structure. 40 | A date and time object contains both a date and a time. 41 | This means you can create an object with just a date or just a time, or both, which gives you huge flexibility in handling dates and times. 42 | 43 | If you run `Get-Date` and specify no parameters, the cmdlet returns the current date and time. 44 | There are several parameters you can specify that allow you to create an object for a particular date, like this: 45 | 46 | ```powershell-console 47 | PS C:\> # Using the -Date Parameter and a date string 48 | PS C:\> Get-Date -Date '1 August 1942' 49 | 01 August 1942 00:00:00 50 | 51 | # Using the -Month, Day, Year to be specific and avoid parsing 52 | PS C:\> Get-Date -Month 8 -Day 1 -Year 1942 -Hour 0 -Minute 0 -Second 0 53 | 01 August 1942 00:00:00 54 | ``` 55 | 56 | You can see the other features of `Get-Date` to help get the date in the exact format you need, see the [`Get-Date` help information](https://docs.microsoft.com/powershell/module/microsoft.powershell.utility/get-date?view=powershell-7.1). 57 | 58 | ## Obtaining Yesterday's Date 59 | 60 | So as you can see, you can use `Get-Date` to return a specific date/time. 61 | So how do you get yesterday's date - or the date or last month or last year? 62 | The trick here is to use the object returned from `Get-Date`. 63 | The object has a type of `System.DateTime` which contains a number of methods allowing you to add increments of time - a month, a day, etc to the object. 64 | 65 | To get yesterday's date (or tomorrow's) you create a date and time object for today using `Get-Date` with no parameters. 66 | Then you use the ``AddDays()`` method to add/subtract some number of days, like this: 67 | 68 | ```powershell-console 69 | PS C:\> # Get today's Date 70 | PS C:\> $Today = Get-Date 71 | PS C:\> $Yesterday = $Today.AddDays(-1) 72 | PS C:\> $Yesterday 73 | 19 February 2021 12:13:51 74 | 75 | PS C:\> # Or more simply 76 | PS C:\> $Yesterday = (Get-Date).AddDays(-1) 77 | PS C:\> $Yesterday 78 | 19 February 2021 12:13:52 79 | 80 | PS C:> # Get tomorrow's date 81 | PS C:> $Tomorrow = (Get-Date).AddDays(1) 82 | PS C:> $Tomorrow 83 | 21 February 2021 12:13:54 84 | ``` 85 | 86 | It is worth noting that a `System.DateTime` object is immutable. 87 | This means you can not change property values after you create the object. 88 | If you use any of the `Add` methods, .NET returns a new object with updated property values. 89 | 90 | ## Using Yesterday's Date 91 | 92 | There are a variety use cases for getting a date in the past (or the future), including: 93 | 94 | * Identifying files that are older/younger than a day/month/etc ago 95 | * Determining which AD Users have not logged on in the last week 96 | * Creating a file name for a file representing last weeks information. 97 | 98 | Here are some examples: 99 | 100 | ```powershell-console 101 | PS C:> # Finding files newer than yesterday 102 | PS C:> $Yesterday = (Get-Date).AddDays(-1) 103 | PS C:> Get-ChildItem | Where-Object LastAccessTime -gt $Yesterday 104 | 105 | Directory: C:\ 106 | 107 | Mode LastWriteTime Length Name 108 | ---- ------------- ------ ---- 109 | -a--- 20/02/2021 14:20 11041 GratefulDead Show List.txt 110 | 111 | PS C:> # Getting users who have logged on in the past day 112 | PS C:> Get-ADUser -Filter * -Property LastLogonDate | Where-Object LastlogonDate -gt $Yesterday 113 | 114 | DistinguishedName : CN=Administrator,CN=Users,DC=cookham,DC=net 115 | Enabled : True 116 | GivenName : Jerry 117 | LastLogonDate : 20/02/2021 04:20:42 118 | Name : Jerry Garcia 119 | ObjectClass : user 120 | ObjectGUID : ae31ca0d-3f01-4eb4-8593-b1d79c71f912 121 | SamAccountName : JerryG 122 | SID : S-1-5-21-2550804810-443649076-1856842782-500 123 | Surname : Garcia 124 | 125 | # Creating a file with yesterday's date 126 | PS C:\> # Creating a file with today's date 127 | PS C:\> $Yesterday = (Get-Date).AddDays(-1).ToString() -replace '/','-' 128 | PS C:\> $YesterdayDate = ($Yesterday -split ' ')[0] 129 | PS C:\> $YesterdayFN = "Results for $YesterdayDate.Txt" 130 | PS C:\> 131 | PS C:\> New-Item -Path C:\Results -Name $YesterdayFN -ItemType File 132 | 133 | Directory: C:\Results 134 | 135 | Mode LastWriteTime Length Name 136 | ---- ------------- ------ ---- 137 | -a--- 20/02/2021 12:56 0 Results for 19-02-2021.Txt 138 | ``` 139 | 140 | In that last example, you need to do a bit of manipulation of the date/time returned by `Get-Date` in order to get a filename that Windows accepts. 141 | This manipulation is needed because `Get-Date` returns a string that contains the "/" character `New-Item` views as a path character. 142 | You use the `-Replace` operator to replace the "/" character with a "-". 143 | Additionally, after performing the replacement, you end up with an (unneeded) time value. 144 | You can use the `-Split` operator to pull out just the date, which is what you want for the file name. 145 | Once you do get the date, you can create you can create a file name for the file. 146 | 147 | Another way to generate the file name based on `Get-Date` would be to use the **ToString()** method and specify the exact output you want, like this: 148 | 149 | ```powershell 150 | $YesterdayDate = (Get-Date).AddDays(-1).ToString('yyyy-MM-dd') 151 | $YesterdayFN = "Results for $YesterdayDate.Txt" 152 | ``` 153 | 154 | Another point worth making is that Windows tries to display dates in a culture-aware way. 155 | `Get-Date` does a fairly good job in most cases of converting a date string into the date you wanted. 156 | But if you want a specific result, using **ToString()** and a date format string is possibly better - and fewer lines of code. 157 | 158 | Needless to say, you could do all those file name manipulations operations as a one-liner. 159 | I leave that as an exercise for you! 160 | 161 | ## Summary 162 | 163 | .NET provides a rich date and time structure (`System.DateTime`). 164 | This structure contains a number of properties such the day, month, hour, millisecond for a given date/time. 165 | You also get a wide range of methods that enable you to manipulate dates by adding or subtracting hours, days, etc. 166 | You can use `Get-Date` cmdlet to get the current date/time or an object for a specific date/time. 167 | Get-Date returns an object of System.DateTime. 168 | You use the methods of the `System.DateTime` structure to get relative dates, such as yesterday, last month or 2 years 42 days, and 32 milliseconds. 169 | 170 | ## Tip of the Hat 171 | 172 | This article is based on an earlier Scripting Guys blog article at [How can I get yesterday's date?](https://devblogs.microsoft.com/scripting/how-can-i-get-yesterdays-date). 173 | Not sure who wrote it. 174 | -------------------------------------------------------------------------------- /Posts/2021/02/media/Announcing-Community-Blog/doctordns-tweet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PowerShell/Community-Blog/7837d320a6bd85ca7d4469dcfaa115112333fac0/Posts/2021/02/media/Announcing-Community-Blog/doctordns-tweet.png -------------------------------------------------------------------------------- /Posts/2021/03/File-System-Watcher-Engine-Event.md: -------------------------------------------------------------------------------- 1 | --- 2 | post_title: A Reusable File System Event Watcher for PowerShell 3 | username: msn 4 | categories: PowerShell 5 | tags: PowerShell, FileSystemWatcher, Module 6 | featured_image: 7 | summary: FSWatcherEngineEvent module provides events of file system changes as powershell engine event 8 | --- 9 | 10 | # A Reusable File System Event Watcher for PowerShell 11 | 12 | Some time ago I wanted to sync files from a source directory to a destination directory immediately after they had changed in the source directory. 13 | As a C# developer I'm aware of a .Net Framework class named '[FileSystemWatcher](/dotnetapi/system.io.filesystemwatcher)' which suits this job perfectly. 14 | A file system watcher listens to change notifications generated by the operating system and invokes a given function if the file change matches several filter criteria like the directory, the file name or the type of the change. 15 | There are already many examples on the internet showing how to create and configure the watcher in PowerShell but this isn't something I can easily recall from memory at the moment I need it. 16 | I made the FSWatcherEngineEvent PowerShell module to make these file system watchers easier to use. 17 | It hides the C#-API behind a PowerShell command with argument completion, it keeps track of the created watchers, and provides commands to pause notifications and to clean up the watchers if they are no longer needed. 18 | 19 | After you install and import the module, you can create a new filesystem watcher. 20 | As an example, you can watch for changes in directory 'C:\Temp\files'. 21 | The command allows to specify the same parameters (with the same names) as if you are using the C# class directly. 22 | This includes: 23 | 24 | - NotifyFilter: what kind of change triggers an event (by default: LastWrite, FileName, DirectoryName) 25 | - Filter: a wildcard to define a subset of files to watch 26 | - IncludeSubdirectory: extends the area of observation to the subdirectories of the specified path 27 | 28 | Please refer to the Microsofts reference documentation of the FileSystemWatcher class for the details. 29 | 30 | ```powershell 31 | New-FileSystemWatcher -SourceIdentifier "MyEvent" -Path C:\Temp\files 32 | ``` 33 | 34 | The watcher now sends notifications to PowerShells engine event queue using the source identifier "MyEvent". 35 | You can consume the event by registering an event handler for the same source identifier. 36 | The following example just writes the whole event converted to JSON to the console: 37 | 38 | ```powershell-console 39 | PS> Register-EngineEvent -SourceIdentifier "MyEvent" -Action { $event | ConvertTo-Json | Write-Host } 40 | 41 | Id Name PSJobTypeName State HasMoreData Location Command 42 | -- ---- ------------- ----- ----------- -------- ------- 43 | 1 MyEvent NotStarted False $event|ConvertTo-Json|Wr… 44 | ``` 45 | 46 | PowerShell allows you to register more than one handler for a single source identifier but the FSWatcherEngineEvent module doesn't allow you to create more than one watcher using the same source identifier. 47 | 48 | To produce a new event, just write some characters to a file in the watched directory: 49 | 50 | ```powershell-console 51 | PS> "XYZ" >> C:\Temp\files\xyz 52 | 53 | { 54 | "ComputerName": null, 55 | "RunspaceId": "b92c271b-c147-4bd6-97e4-ffc2308a1fcc", 56 | "EventIdentifier": 4, 57 | "Sender": { 58 | "NotifyFilter": 19, 59 | "Filters": [], 60 | "EnableRaisingEvents": true, 61 | "Filter": "*", 62 | "IncludeSubdirectories": false, 63 | "InternalBufferSize": 8192, 64 | "Path": "D:\\tmp\\files\\", 65 | "Site": null, 66 | "SynchronizingObject": null, 67 | "Container": null 68 | }, 69 | "SourceEventArgs": null, 70 | "SourceArgs": null, 71 | "SourceIdentifier": "MyEvent", 72 | "TimeGenerated": "2021-03-13T21:39:50.4483088+01:00", 73 | "MessageData": { 74 | "ChangeType": 4, 75 | "FullPath": "C:\\temp\\files\\xyz", 76 | "Name": "xyz" 77 | } 78 | } 79 | ``` 80 | 81 | Events raised before a handler is registered remain in the queue until you consume them using '[Get-Event](xref:Microsoft.PowerShell.Utility.Get-Event)'/'[Remove-Event](xref:Microsoft.PowerShell.Utility.Remove-Event)'. 82 | 83 | To suspend the notification temporarily and to resume it later the following two commands can be used: 84 | 85 | ```powershell 86 | Suspend-FileSystemWatcher -SourceIdentifier "MyEvent" 87 | 88 | Resume-FileSystemWatcher -SourceIdentifier "MyEvent" 89 | ``` 90 | 91 | To keep track of all the filesystem watchers created in the current Powershell process, you can use the command 'Get-FileSystemWatcher': 92 | 93 | ```powershell-console 94 | PS> Get-FileSystemWatcher 95 | 96 | SourceIdentifier : MyEvent 97 | Path : C:\Temp\files\ 98 | NotifyFilter : FileName, DirectoryName, LastWrite 99 | EnableRaisingEvents : True 100 | IncludeSubdirectories : False 101 | Filter : * 102 | ``` 103 | 104 | This command writes a state object to the pipe containing the configuration of all filesystem watchers. 105 | Finally, if you want get rid of filesystem watchers the command 'Remove-FileSystemWatcher' disposes a filesystem watcher specified by the source identifier or by piping in a collection of watchers: 106 | 107 | ```powershell 108 | # to dispose one watcher 109 | Remove-FileSystemWatcher -SourceIdentifier "MyEvent" 110 | 111 | # to dispose all 112 | Get-FileSystemWatcher | Remove-FileSystemWatcher 113 | ``` 114 | 115 | Piping the filesystem watchers also works with the other commands. 116 | If you like the module and want to see more you may inspect or fork its code at [github](https://github.com/wgross/fswatcher-engine-event). 117 | -------------------------------------------------------------------------------- /Posts/2021/03/ScriptingGuy.0006.readingbottomup.md: -------------------------------------------------------------------------------- 1 | --- 2 | post_title: Reading a text file bottom up 3 | username: tfl@psp.co.uk 4 | Catagories: PowerShell 5 | tags: Array 6 | Summary: How can I read a file from the bottom up? 7 | --- 8 | 9 | **Q:** I have a log file in which new data is appended to the end of the file. 10 | That means the most recent entries are at the end of the file. 11 | I’d like to be able to read the file starting with the last line and then ending with the first line, but I can’t figure out how to do that. 12 | 13 | **A:** There are loads of ways you can do this. 14 | A simple way is to use the power of array handling in PowerShell. 15 | 16 | ## The Get-Content Cmdlet 17 | 18 | Before getting into the solution, let's look at the `Get-Content` cmdlet. 19 | The `Get-Content` cmdlet always reads a file from start to finish. 20 | You can always get the very last line of the file like this: 21 | 22 | ```powershell 23 | Get-Content -Path C:\Foo\BigFile.txt | 24 | Select-Object -Last 1 25 | ``` 26 | 27 | This is similar to the `tail` command in Linux. 28 | As is so often the case, the command doesn't quite do what you want it to. 29 | That being said, with PowerShell 7, there's _always_ a way. 30 | 31 | ## Using Arrays 32 | 33 | We can start by reading through the file from top to bottom. 34 | Before displaying those lines to the screen we store them in an array, with each line in the file representing one element in the array. 35 | 36 | As you probably know, an array is a collection of objects. 37 | An array can hold multiple objects of the same, or different, types. 38 | In this case you want the array to hold the lines in your file. 39 | Each line is a string. 40 | Once you have the lines in the array, you can work backwards to achieve your goal. 41 | 42 | ## Creating a simple file 43 | 44 | To demonstrate, let's start by creating a simple file, and output it to a local text file, like this 45 | 46 | ```powershell-console 47 | PS C:\Foo> $File = @' 48 | >> violet 49 | >> indigo 50 | >> blue 51 | >> green 52 | >> yellow 53 | >> orange 54 | >> red 55 | >> '@ 56 | PS C:\Foo> $File | Out-File -Path C:\Foo\SmallFile.txt 57 | PS C:\Foo> Get-ChildItem -Path C:\Foo\SmallFile.txt 58 | 59 | Directory: C:\Foo 60 | 61 | Mode LastWriteTime Length Name 62 | ---- ------------- ------ ---- 63 | -a--- 22/01/2021 20:13 44 SmallFile.txt 64 | ``` 65 | 66 | Once you have created the file, you can get the contents and display it, like this: 67 | 68 | ```powershell-console 69 | PS C:\Foo> $Array = Get-Content -Path C:\Foo\SmallFile.txt 70 | PS C:\Foo> $Array 71 | violet 72 | indigo 73 | blue 74 | green 75 | yellow 76 | orange 77 | red 78 | ``` 79 | 80 | Admittedly, all we seem to have done so far is get back to where we started - displaying the file from the start to the finish not the reverse. 81 | So how do we get to where you want to go? 82 | 83 | ## Arrays vs text files 84 | 85 | There’s an important difference between a text file and an array. 86 | With a text file, using `Get-Content`, you read it from only from the start to the finish. 87 | Windows, .NET, and PowerShell do not provide a way to read the file in reverse. 88 | However, once you have the file contained in an array. it’s easy to read the array from the bottom to the top. 89 | 90 | Let's start by working out how many lines there are in the array. 91 | And, more as a sanity check, display how many lines there are in the file, like this: 92 | 93 | ```powershell-console 94 | PS C:\Foo> $Array = Get-Content -Path C:\Foo\SmallFile.txt 95 | PS C:\Foo> $Length = $Array.count 96 | PS C:\Foo> "There are $Length lines in the file" 97 | There are 7 lines in the file 98 | ``` 99 | 100 | So that tells us you have the number of lines in the array that you expected. 101 | 102 | ## Getting Array Members 103 | 104 | So let's give you a solution. 105 | In our sample array, `$Array` we have 7 lines. 106 | We can address any individual array member directly using `[<index>]` syntax (after the array name). 107 | So the first item in the array always has an index number of 0 or `$Array[0]`). 108 | In our array, the line **violet** has an index number of 0 so you can get to it using `$Array[0]`. 109 | Likewise, red has an index number of 6, or `$Array[6]`. 110 | But that doesn't help us much - just yet! 111 | 112 | A particularly neat feature of array handling in PowerShell is that we can work backwards in an array using negative index values. 113 | An index of [-1] is always the last element of an array, [-2] is the penultimate line, and so on. 114 | So `$Array[-1]` is Red, `$Array[-2]` is Orange, and so on. 115 | 116 | So what we do is to look first at `$Array[-1]`, then `$Array[-2]`, and so on, all withing a simple foreach loop, like this: 117 | 118 | ```powershell-console 119 | PS C:\Foo> $Array = Get-Content -Path C:\Foo\SmallFile.txt 120 | PS C:\Foo> $Length = $Array.count 121 | PS C:\Foo> "There are $Length lines in the file" 122 | There are 7 lines in the file 123 | PS C:\Foo> $Line = 1 124 | PS C:\Foo> 1..$Length | ForEach-Object {$Array[-$Line]; $Line++} 125 | red 126 | orange 127 | yellow 128 | green 129 | blue 130 | indigo 131 | violet 132 | ``` 133 | 134 | This code snippet first sets a variable, `$Line`, to 1. 135 | Then you read the file and display how many lines are in the file. 136 | You then use `ForEach-Object` to run a script block once for each line in the file. 137 | Inside the script block you get the array element starting at the end and output it to the console. 138 | Then you increment the line number and repeat. 139 | 140 | This may be a little confusing if you haven't work with arrays, but once you get the hang of it, you see how simple it really is. 141 | Arrays are a fantastic capability within PowerShell. 142 | 143 | ## For More Information 144 | 145 | For more information on arrays in PowerShell, see [About_Arrays](https://docs.microsoft.com/powershell/module/microsoft.powershell.core/about/about_arrays). 146 | And for more information on `Get-Content` see the [Get-Content](https://docs.microsoft.com/powershell/module/microsoft.powershell.management/get-content) help page. 147 | 148 | ## Summary 149 | 150 | So as you saw, `Get-Content` does not read backwards through a file. 151 | If you bring the file contents into an array, you can easily read it backwards. 152 | 153 | ## Tip of the Hat 154 | 155 | This article is based on an earlier Scripting Guys blog article at [Can I Read a Text file from the Bottom Up?](https://devblogs.microsoft.com/scripting/can-i-read-a-text-file-from-the-bottom-up/). 156 | I am not sure who wrote the original article. 157 | -------------------------------------------------------------------------------- /Posts/2021/03/ScriptingGuy.0007.folderexists.md: -------------------------------------------------------------------------------- 1 | --- 2 | post_title: Determine if a folder exists 3 | username: baumanisf 4 | Catagories: PowerShell 5 | tags: File, Test-Path 6 | Summary: How can I determine if a folder exists? 7 | --- 8 | 9 | **Q:** Is there any way to determine whether or not a specific folder exists on a computer? 10 | **A:** There are loads of ways you can do this. 11 | 12 | ## The Test-Path Cmdlet 13 | 14 | The easiest way to do this is to use the `Test-Path` cmdlet. 15 | It looks for a given path and returns `True` if it exists, otherwise it returns `False`. 16 | You could evaluate the result of the `Test-Path` like in the code snippet below 17 | 18 | ```powershell 19 | $Folder = 'C:\\Windows' 20 | "Test to see if folder [$Folder] exists" 21 | if (Test-Path -Path $Folder) { 22 | "Path exists!" 23 | } else { 24 | "Path doesn't exist." 25 | } 26 | ``` 27 | This is similar to the `-d $filepath` operator for IF statements in Bash. `True` is returned if `$filepath` exists, otherwise `False` is returned. 28 | 29 | ## For More Information 30 | 31 | And for more information on `Test-Path` see the [Test-Path](https://docs.microsoft.com/powershell/module/microsoft.powershell.management/test-path) help page. 32 | 33 | ## Summary 34 | 35 | So as you saw, `Test-Path` tests the existence of a path and returns a boolean value. 36 | This return value can be evaluated in a IF statement for example. 37 | ## Tip of the Hat 38 | 39 | This article is based on an earlier Scripting Guys blog article at [How can I determine if a folder exists on a computer?](https://devblogs.microsoft.com/scripting/how-can-i-determine-if-a-folder-exists-on-a-computer/). 40 | I am not sure who wrote the original article. 41 | -------------------------------------------------------------------------------- /Posts/2021/03/media/DarwinJS-Contribution-LightningFastandEasyProvisioningofGitwithSSHKeyAuthenticationonWindows/windows-git-ssh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PowerShell/Community-Blog/7837d320a6bd85ca7d4469dcfaa115112333fac0/Posts/2021/03/media/DarwinJS-Contribution-LightningFastandEasyProvisioningofGitwithSSHKeyAuthenticationonWindows/windows-git-ssh.png -------------------------------------------------------------------------------- /Posts/2021/04/NewTemporaryFolders.md: -------------------------------------------------------------------------------- 1 | --- 2 | post_title: Borrowing a built-in PowerShell command to create a temporary folder 3 | username: sean.kearney@microsoft.com 4 | Catagories: PowerShell, Function 5 | tags: Function,Fun trick,Existing Cmdlet,New Purpose 6 | Summary: Leveraging a built-in cmdlet in a new and interesting way 7 | --- 8 | 9 | **Q:** Hey I have a question for you. It seems silly and I know I could probably put something together with Get-Random. But can you think of another way to create a temporary folder with a random name in PowerShell? 10 | 11 | Ideally, I'd like it to be in a user's own "Temporary Folder" is possible. 12 | 13 | **A:** We sure can! If Doctor Scripto was sitting here right now, I'd see that little green haired person shout out "Never fear, Scripto is here!" 14 | 15 | ## New-TemporaryFile Cmdlet 16 | 17 | Within PowerShell there is a built in Cmdlet called `New-TemporaryFile`. Running this cmdlet simply creates a random 0 byte file in the `$ENV:Temp folder` in whichever platform you are working in. 18 | 19 | However, we can *borrow* the filename created and use it to create a folder instead. It’s not really difficult, but maybe just not thought of very often. 20 | 21 | When we execute the following cmdlet we get output similar to this as it generates a new 0 Byte random file in the User's Temp folder stored in `$ENV:Temp` 22 | 23 | ```output 24 | PS> New-TemporaryFile 25 | 26 | Mode LastWriteTime Length Name 27 | ---- ------------- ------ ---- 28 | -a---- 3/31/2021 9:25 PM 0 tmpA927.tmp 29 | ``` 30 | 31 | Ok, that really wasn’t that impressive but what if we were to do this instead? 32 | 33 | ```powershell 34 | $File = New-TemporaryFile 35 | ``` 36 | 37 | Now we’ve created the file and stored it away in the `$File` object. With this we can remove the file of course using the `Remove-Item` cmdlet 38 | 39 | ```powershell 40 | Remove-Item -path $File -force 41 | ``` 42 | 43 | HA! I’ve already saved some time! The `$File` object is still there with the information I want to use. 44 | 45 | So, I could access the name in the object property and use it to create a directory instead in the following manner. 46 | 47 | ```powershell 48 | New-Item -ItemType Directory -Path $File.Name 49 | ``` 50 | 51 | But the problem is that it would be in whatever default folder PowerShell was looking into at the time. 52 | 53 | Hmmmmm…. How to solve that? 54 | 55 | But there is a built in variable called `$ENV:Temp` which targets the exact Temporary folder that the `New-TemporaryFile` cmdlet uses as well! 56 | 57 | I can then take that variable and the original name of the Temporary file and combine them together like this. 58 | 59 | ```powershell 60 | $ENV:Temp + '\' + $File.Name 61 | ``` 62 | 63 | *or* 64 | 65 | I can even put them together in a single String like this. 66 | 67 | ```powershell 68 | "$($ENV:Temp)\$($File.Name)" 69 | ``` 70 | 71 | With this I could just create a new temporary directory under our temp folder in this manner. 72 | 73 | ```powershell 74 | New-Item -ItemType Directory -Path "$($ENV:Temp)\$($File.Name)" 75 | ``` 76 | 77 | Now to identify where the file ended up, I could same thing as last time by storing it as an object like `$DirectoryName` if I wanted. Then I could remove the "Random Directory name" later if I needed to. 78 | 79 | ```powershell 80 | $Folder=New-Item -ItemType Directory -Path "$($ENV:Temp)\$($File.Name)" 81 | ``` 82 | 83 | Then when I am done with that folder that was presumably used to hold some garbage data. I can just use `Remove-Item` again. 84 | 85 | But because it's a directory, I need to add `-recurse -force` to ensure all data and Subfolders are removed. 86 | 87 | ```powershell 88 | Remove-Item -Path $Folder -Recurse -Force 89 | ``` 90 | 91 | But here is the fun and neat bit. If you needed on a regular basis, we could make this into a quick function for your code, module or to impress friends with! 92 | 93 | ```powershell 94 | Function New-TemporaryFolder { 95 | # Create Temporary File and store object in $T 96 | $File = New-TemporaryFile 97 | 98 | # Remove the temporary file .... Muah ha ha ha haaaaa! 99 | Remove-Item $File -Force 100 | 101 | # Make a new folder based upon the old name 102 | New-Item -Itemtype Directory -Path "$($ENV:Temp)\$($File.Name)" 103 | } 104 | ``` 105 | 106 | Now at this point I had thought my journey was complete. It was until I posted the solution to the [Facebook group for the PowerShell Community Blog](https://www.facebook.com/groups/pscommunityblog/) to share. 107 | 108 | A fellow member of the Community noted the approach, while neat, was not very efficient. 109 | 110 | At that point I dug into the code on Github for the open source version of PowerShell 7.x to see how it was done there. 111 | 112 | In reading the source code for `New-TemporaryItem` I was able to see the .NET object being used to generate the file. It turns out there is also a .NET method that can be used to create just that temporary name which all I wanted to use in the first place for the directory name. 113 | 114 | When I ran this in the PowerShell Console it produced the following output of a New Temporary Folder 115 | 116 | ```output 117 | PS> [System.IO.Path]::GetTempFileName() 118 | C:\Users\Administrator\AppData\Local\Temp\2\tmp3864.tmp</code></pre> 119 | ``` 120 | 121 | This was exactly what I wanted, that random temporary Name to be consumed for the `New-Item` Cmdlet. With this approach the function became a lot simpler and far more efficient! 122 | 123 | ```powershell 124 | Function New-TemporaryFolder { 125 | # Make a new folder based upon a TempFileName 126 | New-Item -ItemType Directory -Path([System.IO.Path]::GetTempFileName()) 127 | } 128 | ``` 129 | 130 | But alas my victory was short lived. This method still created the file, it didn't just display the name. So the function ended up failing. 131 | 132 | But since really I just wanted that format to be used for the temporary directory. Plus the format for the temporary filename was as simple as `tmpxxxx.tmp` where the xxxx was a random hexadecimal number, I came up with a better idea! 133 | 134 | Just create a number between 0 and 65535 with `Get-Random` and use the `[convert]` accelerator to change it the 4 character Hexadecimal number instead. 135 | 136 | The end result looked like this and gave me the desired result I wanted. 137 | 138 | ```output 139 | PS> "$($Env:temp)\tmp$([convert]::tostring((get-random 65535),16).padleft(4,'0')).tmp" 140 | C:\Users\doctorscripto\AppData\Local\Temp\tmp5633.tmp 141 | ``` 142 | 143 | Now I ended up with a working function that could produce the desired output I wanted and in a more efficient manner. 144 | 145 | ```powershell 146 | >Function New-TemporaryFolder { 147 | # Make a new folder based upon a TempFileName 148 | $T="$($Env:temp)\tmp$([convert]::tostring((get-random 65535),16).padleft(4,'0')).tmp" 149 | New-Item -ItemType Directory -Path $T 150 | } 151 | ``` 152 | 153 | Why did all of this pop into my head? I was actually creating some PowerShell for customer and needed a consistent and random set of folders in a common and easily erasable location. 154 | 155 | I was hoping that we had a `New-TemporaryDirectory` cmdlet, but found it was just as easy to write one by *borrowing* an existing cmdlet. 156 | 157 | It was fun as well to discover how I could improve on the solution by reading the [Source code for New-TemporaryItem](https://github.com/PowerShell/PowerShell/blob/master/src/Microsoft.PowerShell.Commands.Utility/commands/utility/NewTemporaryFileCommand.cs) on Github. 158 | 159 | Thanks to a little nudging from the Community. So a big Thank you to Joel Bennett for the critique! :) 160 | 161 | Sean Kearney - Customer Engineer/Microsoft - @PowerShellMan 162 | 163 | _*"Remember with great PowerShell comes great responsibilty..."*_ 164 | 165 | -------------------------------------------------------------------------------- /Posts/2021/04/tfl-IsUserALocalAdministrator.md: -------------------------------------------------------------------------------- 1 | --- 2 | post_title: Is a User A Local Administrator 3 | username: tfl@psp.co.uk 4 | Catagories: PowerShell 5 | tags: local users, security, logon scripts 6 | Summary: In a logon script, how you can tell if the user is a local administrator 7 | --- 8 | 9 | **Q:** Some of the things we do in our logon scripts require the user to be a local administrator. How can the script tell if the user is a local administrator or not, using PowerShell 7. 10 | 11 | **A:** Easy using PowerShell 7 and the LocalAccounts module 12 | 13 | ## Local Users and Groups 14 | 15 | The simple answer is of course, easily. 16 | And since you ask, with PowerShell 7! 17 | But let's begin lets begin by reviewing local users and groups in Windows. 18 | 19 | Every Windows system, except for Domain Controllers, maintains a set of local accounts - local users and local groups. 20 | Domain controllers use the AD and do not really have local accounts as such. 21 | You use these local accounts in addition to domain users and domain groups on domain-joined hosts when setting permissions. 22 | You can logon to a given server using a local account or a domain account. 23 | On Domain Controllers you can only login using a domain account. 24 | 25 | As with AD groups, local groups and local users each have a unique Security ID (SID). 26 | When you give a local user or group access to a file or folder, Windows adds that SID to the object's Access Control List. 27 | This is the same way Windows enables you to give permissions to a local file or folder to any Active DIrectory user or group. 28 | 29 | Additionally, Windows and some Windows features create "well known" local groups. 30 | The intention is that you add users to these groups to enable those users to perform specific administrative functions on just those servers. 31 | 32 | Traditionally, you might have used the `Wscript.Network` COM object, in conjunction with ADSI. 33 | You can, of course, use the older approach in side PowerShell 7, but why bother? 34 | The good news with PowerShell 7, you can use the `Microsoft.PowerShell.LocalAccounts` module to manage local accounts. 35 | At the time of writing, this is a Windows only module. 36 | 37 | ## The Microsoft.PowerShell.LocalAccounts module 38 | 39 | In PowerShell 7 for Windows, you can use the `Microsoft.PowerShell.LocalAccounts` module to manage local users and group. 40 | This module is a Windows PowerShell module which PowerShell 7 loads from `C:\WINDOWS\system32\WindowsPowerShell\v1.0\Modules\Microsoft.PowerShell.LocalAccounts`. 41 | 42 | This module contains 15 cmdlets, which you can view like this: 43 | 44 | ```powershell-console 45 | PS> Get-Command -Module Microsoft.PowerShell.LocalAccounts 46 | 47 | CommandType Name Version Source 48 | ----------- ---- ------- ------ 49 | Cmdlet Add-LocalGroupMember 1.0.0.0 Microsoft.PowerShell.LocalAccounts 50 | Cmdlet Disable-LocalUser 1.0.0.0 Microsoft.PowerShell.LocalAccounts 51 | Cmdlet Enable-LocalUser 1.0.0.0 Microsoft.PowerShell.LocalAccounts 52 | Cmdlet Get-LocalGroup 1.0.0.0 Microsoft.PowerShell.LocalAccounts 53 | Cmdlet Get-LocalGroupMember 1.0.0.0 Microsoft.PowerShell.LocalAccounts 54 | Cmdlet Get-LocalUser 1.0.0.0 Microsoft.PowerShell.LocalAccounts 55 | Cmdlet New-LocalGroup 1.0.0.0 Microsoft.PowerShell.LocalAccounts 56 | Cmdlet New-LocalUser 1.0.0.0 Microsoft.PowerShell.LocalAccounts 57 | Cmdlet Remove-LocalGroup 1.0.0.0 Microsoft.PowerShell.LocalAccounts 58 | Cmdlet Remove-LocalGroupMember 1.0.0.0 Microsoft.PowerShell.LocalAccounts 59 | Cmdlet Remove-LocalUser 1.0.0.0 Microsoft.PowerShell.LocalAccounts 60 | Cmdlet Rename-LocalGroup 1.0.0.0 Microsoft.PowerShell.LocalAccounts 61 | Cmdlet Rename-LocalUser 1.0.0.0 Microsoft.PowerShell.LocalAccounts 62 | Cmdlet Set-LocalGroup 1.0.0.0 Microsoft.PowerShell.LocalAccounts 63 | Cmdlet Set-LocalUser 1.0.0.0 Microsoft.PowerShell.LocalAccounts 64 | ``` 65 | 66 | As you can tell, these cmdlets allow you to add, remove, change, enable and disable a local user or local group 67 | And they allow you to add, remove and get the local group's members. 68 | These cmdlets are broadly similar to the ActiveDirectory cmdlets, but work on local users. 69 | And as noted above, you can use domain users/groups as a member of a local group should you wish or need to. 70 | 71 | You use the `Get-LocalGroupMember` command to view the members of a local group, like this: 72 | 73 | ```powershell-console 74 | PS> Get-LocalGroupMember -Group 'Administrators' 75 | 76 | ObjectClass Name PrincipalSource 77 | ----------- ---- --------------- 78 | Group COOKHAM\Domain Admins ActiveDirectory 79 | User COOKHAM24\Administrator Local 80 | User COOKHAM\JerryG ActiveDirectory 81 | User COOKHAM24\Dave Local 82 | ``` 83 | 84 | As you can see in this output, the local Administrators group on this host contains domain users and groups as well as local users 85 | 86 | ## Is the User an Administrator? 87 | 88 | It's easy to get membership of any local group, as you saw above. 89 | But what if you want to find out if a given user is a member of some local administrative group? 90 | That too is pretty easy and take a couple of steps. 91 | One way you can get the name of the current user is by using `whoami.exe`. 92 | Then you can get the members of the local administrator's group. 93 | Finally, you check to see if the currently logged on user is a member of the group. 94 | All of which looks like this: 95 | 96 | ```powershell-console 97 | PS> # Get who I am 98 | PS> $Me = whoami.exe 99 | PS> $Me 100 | Cookham\JerryG 101 | 102 | PS> # Get members of administrators group 103 | PS> $Admins = Get-LocalGroupMember -Name Administrators | 104 | Select-Object -ExpandProperty name 105 | 106 | PS> # Check to see if this user is an administrator and act accordingly 107 | PS> if ($Admins -Contains $Me) { 108 | "$Me is a local administrator"} 109 | else { 110 | "$Me is NOT a local administrator"} 111 | Cookham\JerryG is a local administrator 112 | ``` 113 | 114 | If the administrative group contains user running the script, then `$Me` is a user in that local admin group. 115 | 116 | In this snippet, we just echo the fact that the user is, ir is not, a member of the local administrators group. 117 | You can adapt it to ensure a user is a member of the appropriate group before attempting to run certain commands. 118 | And you can also adapt it to check for membership in other local groups such as **Backup Operators** or **Hyper-V Users** which may be relevant. 119 | 120 | In your logon script, once you know that the user is a member of a local administrative group, you can carry out any tasks that require that membership. 121 | And if the user is not a member of the group, you could echo that fact, and avoid using the relevant cmdlets. 122 | 123 | ## Summary 124 | 125 | Using the Local Accounts module in PowerShell 7, it's easy to manage local groups! 126 | You can, of course, manage the groups the same way in Windows PowerShell. 127 | 128 | ## Tip of the Hat 129 | 130 | This article was originally a VBS based solution as described in (an earlier blog post)[https://devblogs.microsoft.com/scripting/how-can-i-determine-if-a-user-is-a-local-administrator/]. 131 | I am not sure who the author of the original post was - but thanks. 132 | -------------------------------------------------------------------------------- /Posts/2021/05/SendingDataToTheClipBoard.md: -------------------------------------------------------------------------------- 1 | --- 2 | post_title: Sending data to the Clipboard from PowerShell 3 | username: sean.kearney@microsoft.com 4 | Categories: PowerShell, vbScript, Conversion, Cmdlet 5 | tags: PowerShell, Clipboard, vbScript 6 | Summary: Sending data to the clipboard from all versions of PowerShell 7 | --- 8 | 9 | **Q:** Hey I have a fun question! I remember reading a while back about using 10 | VBScript to paste to the clipboard. Are we able to do that with PowerShell? 11 | 12 | **A:** Why yes, yes we can! It is far often a much quicker solution if we 13 | start with PowerShell! 14 | 15 | ## Pasting content to the clipboard, the old VBScript method 16 | 17 | Before we show the quick and easy solution, let's learn how we could adapt an 18 | older solution. 19 | 20 | Now back in the day if I wanted to paste something on the clipboard I would go 21 | down to the store, get some glue and.... 22 | 23 | "DOH! Wrong Clipboard!" (Knew I should have splashed some water on my face 24 | before typing this up!) 25 | 26 | What I should have said was back before PowerShell existed we actually had TWO 27 | methods to paste text data to the clipboard. 28 | 29 | One was a nice simple solution if you were working in DOS or has simple text 30 | output from a VBScript. You would pipe the output to the `clip` command as 31 | seen below 32 | 33 | ```output 34 | dir | clip 35 | ``` 36 | 37 | Once this was complete you could paste your captured text using a `CTRL-V` in 38 | whichever Windows application. 39 | 40 | Another method that presented itself was using some code such as this in 41 | VBScript 42 | 43 | ```VBScript 44 | strCopy = "This text has been copied to the clipboard." 45 | Set objIE = CreateObject("InternetExplorer.Application") 46 | objIE.Navigate("about:blank") 47 | objIE.document.parentwindow.clipboardData.SetData "text", strCopy 48 | objIE.Quit 49 | ``` 50 | 51 | So I could re-use this solution in PowerShell quite easily. I do this in case 52 | you might ever see some older VBScript that you might want to reuse. 53 | 54 | The first line in VBScript is assigning a string 55 | 56 | ```VBScript 57 | strCopy = "This text has been copied to the clipboard." 58 | ``` 59 | 60 | In PowerShell I can do this in the following manner. 61 | 62 | ```powershell 63 | $strCopy = "This text has been copied to the clipboard." 64 | ``` 65 | 66 | The next line is where a Comobject is created. 67 | 68 | ```VBScript 69 | Set objIE = CreateObject("InternetExplorer.Application") 70 | ``` 71 | 72 | The equivalent code in PowerShell to do the same thing and even use the same 73 | variable name for the object would be 74 | 75 | ```powershell 76 | # Create a connect to Internet Explorer and launch it as a 77 | # hidden application 78 | $objIE = New-Object -comobject "InternetExplorer.Application" 79 | ``` 80 | 81 | Then from this point the final lines are just manipulating data in the Object. 82 | 83 | ```VBScript 84 | objIE.Navigate("about:blank") 85 | objIE.document.parentwindow.clipboardData.SetData "text", strCopy 86 | objIE.Quit 87 | ``` 88 | 89 | Which in PowerShell would look like this. 90 | 91 | ```powershell 92 | # Point Internet Explorer to "Blank page" 93 | $objIE.Navigate("about:blank") 94 | 95 | # Leverage Internet explorer to send content to the clipboard 96 | $objIE.document.parentwindow.clipboardData.SetData("text", $strCopy) 97 | 98 | # A job well done! Quit and go back home 99 | $objIE.Quit 100 | ``` 101 | 102 | However if you try this solution in a modern version of Windows, it will appear 103 | to just sit and hang in PowerShell. 104 | 105 | We can one extra line to the original code and you see why. 106 | 107 | ```powershell 108 | $objIE.Navigate("about:blank") 109 | 110 | # Show the hidden Internet Explorer background application 111 | $objIE.Visible=$True 112 | 113 | $objIE.document.parentwindow.clipboardData.SetData("text", strCopy) 114 | $objIE.Quit 115 | ``` 116 | 117 | The following Window below demonstrates why the old solution, even when 118 | converted to PowerShell failed. 119 | 120 | ![Prompt To Allow or Deny Clipboard Paste in Internet Explorer](./media/SendingDataToTheClipBoard/InteractivePromptStoppingTheOldSolution.PNG) 121 | 122 | In fact, even if we just ran it in vbScript today, it would have failed in an 123 | equal manner. 124 | 125 | ## The drawback to converting from VBScript 126 | 127 | So that was pretty cool, you tried to re-use some VBScript to meet your task. 128 | In this case because security has improved in past 17 years, Internet Explorer 129 | is not allowed to just paste things to the clipboard. 130 | 131 | But although this is a nice way to learn how to convert over some older code 132 | from VBScript, it is actually not a good used of PowerShell for two reasons. 133 | 134 | 1. It leverages Internet Explorer which, for this purpose, is a big resource 135 | to solve the problem at hand. We can also no longer automate in this manner. 136 | 1. PowerShell has built in cmdlets to solve the problem which are far easier 137 | to use. They not only work well in Windows PowerShell, but also just as 138 | seamlessly across all supported Operating Systems when using PowerShell 7.x 139 | 140 | ## The clipboard cmdlets in PowerShell 141 | 142 | You can verify they exist by just using `Get-Command` like the sample below 143 | 144 | ```output 145 | Get-Command *clipboard* 146 | 147 | CommandType Name Version Source 148 | ----------- ---- ------- ------ 149 | Cmdlet Get-Clipboard 7.0.0.0 Microsoft.PowerShell.Management 150 | Cmdlet Set-Clipboard 7.0.0.0 Microsoft.PowerShell.Management 151 | ``` 152 | 153 | To populate the clipboard with a directory structure, as an example, I can 154 | execute the following line 155 | 156 | ```output 157 | PS> Get-Childitem | Set-Clipboard 158 | ``` 159 | 160 | There is no visual output because the data is now stored on the Clipboard. 161 | 162 | To verify this in PowerShell you can use the `Get-Clipboard` Cmdlet. 163 | 164 | ```output 165 | PS> Get-Clipboard 166 | C:\Demo\AzureADBaseline 167 | C:\Demo\AzureDSC 168 | C:\Demo\AzureVM-Json 169 | C:\Demo\DualStateMitigate 170 | C:\Demo\testmoduleforme.ps1 171 | C:\Demo\TheShellofBlueness.docx 172 | ``` 173 | 174 | So yes, I wasted a bit of time showing you how to convert some VBScript. 175 | Please have pity on me for my intent was good. Shame on me. :) 176 | 177 | ## Summary 178 | 179 | It is nice to know that you can convert over to PowerShell in a pretty 180 | simple manner from vbScript if you needed to. There are a lot of excellent 181 | examples of how to manage Windows environments with VBScript readily written. 182 | 183 | It is equally important to understand why we would choose to start fresh with 184 | the solution in PowerShell. 185 | 186 | It gives us a solution, in this case of manipulating the clipboard in an 187 | Operating System; which is consistent across the board whether you choose to 188 | use Windows, MacOS, Linux or any supported Operating System for PowerShell 7.x. 189 | 190 | The choice is yours my friends! 191 | 192 | ## Tip of the Hat 193 | 194 | This article was based on one written back on older post on 195 | ["Can I Copy Script Output to the Clipboard"](https://devblogs.microsoft.com/scripting/can-i-copy-script-output-to-the-clipboard/) 196 | 197 | I do not recall the author but it was a good way to learn how to 198 | programmatically set the clipboard back then. It was time to get it updated. 199 | 200 | Cheers all! Your friend in Automation 201 | Sean Kearney - Customer Engineer/Microsoft - @PowerShellMan 202 | 203 | _"Remember with great PowerShell comes great responsibilty..."_ 204 | -------------------------------------------------------------------------------- /Posts/2021/05/media/SendingDataToTheClipBoard/InteractivePromptStoppingTheOldSolution.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PowerShell/Community-Blog/7837d320a6bd85ca7d4469dcfaa115112333fac0/Posts/2021/05/media/SendingDataToTheClipBoard/InteractivePromptStoppingTheOldSolution.PNG -------------------------------------------------------------------------------- /Posts/2021/06/media/tfl-edgestart/tfl-edgestgart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PowerShell/Community-Blog/7837d320a6bd85ca7d4469dcfaa115112333fac0/Posts/2021/06/media/tfl-edgestart/tfl-edgestgart.png -------------------------------------------------------------------------------- /Posts/2021/06/tfl-edgestart.md: -------------------------------------------------------------------------------- 1 | --- 2 | post_title: How to Change the Start Page for the Edge Browser 3 | username: tfl@psp.co.uk 4 | Categories: PowerShell 5 | tags: PowerShell, Edge 6 | Summary: How to configure start page for the MS Edge web browser. 7 | --- 8 | **Q:** How can I change the Edge startup page? 9 | 10 | **A:** You can change the start page easily using PowerShell. 11 | 12 | ## Edge and It's Start Page 13 | 14 | I am basing this article on the latest incarnation of the Edge browser, aka Edge Chromium. 15 | The settings in this article seem to work fine on the latest versions of Windows 10 and Server 2022. 16 | Other browsers can have different approaches to startup page. 17 | And as ever - E&OE! 18 | 19 | When the Edge browser starts up, it displays a startup page based on Bing by default. 20 | For many, this is fine - they can browse the contents and then navigate where they want. 21 | But in some circumstances, you may wish to change this default. 22 | And fortunately, this is straightforward to achieve. 23 | 24 | An easy way to set the startup page for yourself is to configure two registry value entries on the local machine. 25 | The first is the **RestoreOnStartup** value entry to the registry key `HKCU:\\Software\\Policies\\Microsoft\\Edge`. 26 | This value entry is a **REG_DWORD**. 27 | By setting this entry with a value of **4**, you tell Edge to use the URL or URLs you specify when it starts up rather than the default home page. 28 | 29 | The second value entry (or entries) is within the key `HKCU:\\Software\\Policies\\Microsoft\\Edge\\RestoreOnStartupURLs`. 30 | This value entry (or entries) contains the URL (or URLS) you want Edge to open at startup. 31 | In most cases, you would setup a single URL under this key, but you can set more to have Edge bring up multiple pages at startup. 32 | 33 | Each registry value entry must have a unique entry name and contain the value of a URL you want Edge to restore at startup. 34 | The value entry name doesn't seem to matter, so a value of **1** is fine. 35 | If you want a second URL, then add a second value entry with a name of **2** (and the value of the second URL). 36 | 37 | Creating and setting these keys and key values as shown below enables the current user's settings. 38 | If you are sharing the host with multiple users and want all users to have the same start page, you can set these entries in `HKCU:\\Software\\Policies` instead. 39 | 40 | ## Configuring Edge Chromium Home Page 41 | 42 | You have choices as to what page of pages Edge opens when it starts. 43 | You could choose a corporate Intranet landing page or perhaps a SharePoint project site. 44 | You could also the startup page to a different search engine home page, such as Duck Duck Go. 45 | As noted above, you can configure multiple startup pages should that be useful. 46 | 47 | With PowerShell, you use the `New-Item` cmdlet to create the registry keys. 48 | It also makes sense to test whether the keys exist before trying to create them (and generating an error). 49 | You set the value entries using the `Set-ItemProperty` cmdlet. 50 | 51 | The following code snippet sets the default home page for the current user for Edge Chromium to [DudkDuckGo](https://duckduckgo.com/). 52 | 53 | ```powershell 54 | # Ensure Edge key exists 55 | $EdgeHome = 'HKCU:\\Software\\Policies\\Microsoft\\Edge' 56 | If ( -Not (Test-Path $EdgeHome)) { 57 | New-Item -Path $EdgeHome | Out-Null 58 | } 59 | # Set RestoreOnStartup value entry 60 | $IPHT = @{ 61 | Path = $EdgeHome 62 | Name = 'RestoreOnStartup' 63 | Value = 4 64 | Type = 'DWORD' 65 | } 66 | Set-ItemProperty @IPHT -verbose 67 | # Create Startup URL's registry key 68 | $EdgeSUURL = "$EdgeHome\\RestoreOnStartupURLs" 69 | If ( -Not (Test-Path $EdgeSUURL)) { 70 | New-Item -Path $EdgeSUURL | Out-Null 71 | } 72 | # Create a single URL startup page 73 | $HOMEURL = 'https://duckduckgo.com' 74 | Set-ItemProperty -Path $EdgeSUURL -Name '1' -Value $HomeURL 75 | ``` 76 | 77 | The next time you start Edge, you should see this: 78 | 79 | ![Running Edge with new startup page](./media/tfl-edgestart/tfl-edgestgart.png) 80 | 81 | 82 | ## There are other ways 83 | 84 | As ever with PowerShell, there are other ways you could change start page for Edge. 85 | You can [use the browser itself as described in a Microsoft suuport article](https://support.microsoft.com/en-us/microsoft-edge/change-your-browser-home-page-a531e1b8-ed54-d057-0262-cc5983a065c6). 86 | As ever, you coulkd set the registry settings using WMI. 87 | And, last but not least, it's [straightforward to set the browser's start page via group policy](https://admx.help/?Category=Windows_10_2016&Policy=Microsoft.Policies.MicrosoftEdge::HomePages). 88 | 89 | ## Summary 90 | 91 | It is easy to change the start page for the Edge web browser 92 | you just have to set a fewe registry keys and job done. 93 | 94 | ## Tip of the Hat 95 | 96 | I based this article on one written for the earlier Scripting Guys blog [How Can I Change the Internet Explorer Home Page?](https://devblogs.microsoft.com/scripting/how-can-i-change-the-internet-explorer-home-page/). 97 | I don't know the author. 98 | -------------------------------------------------------------------------------- /Posts/2021/06/tfl-rename-nic.md: -------------------------------------------------------------------------------- 1 | --- 2 | post_title: How to rename a NIC 3 | username: tfl@psp.co.uk 4 | Categories: PowerShell 5 | tags: PowerShell, network, NIC 6 | Summary: How to rename a NIC using PowerShell 7 | --- 8 | **Q:** Is there a simple way to rename a NIC, especially inside a Hyper-V VM? 9 | 10 | **A:** You can change the name of any Windows NIC using PowerShell - whether the NIC is in a physical host or a Hyper-V VM. 11 | 12 | 13 | ## NICS and NIC names 14 | 15 | One thing that can quickly become confusing when using Hyper-V with multiple VMs and VM Switches is how fast the network adapters seem to proliferate. 16 | You start with a few wired Ethernet Adapters on the host. 17 | Then you install Hyper-V and create a VM farm with loads of virtual NICs. 18 | Before you know it, you have a dozen adapters inside the VM host and an unclear set of adapters in the VM. 19 | 20 | To discover the NICs in a host or a VM, you use the `Get-NetAdapter` cmdlet. 21 | Which looks like this inside a Hyper-V VM: 22 | 23 | ```powershell-Console 24 | PS> Get-NetAdapter 25 | 26 | Name InterfaceDescription ifIndex Status MacAddress LinkSpeed 27 | ---- -------------------- ------- ------ ---------- --------- 28 | Ethernet Microsoft Hyper-V Network Adapter 22 Up 00-15-5D-01-2A-91 10 Gbps 29 | Ethernet 2 Microsoft Hyper-V Network Adapter #2 14 Up 00-15-5D-01-2A-92 10 Gbps 30 | Ethernet 3 Microsoft Hyper-V Network Adapter #3 15 Up 00-15-5D-01-2A-92 10 Gbps 31 | Ethernet 4 Microsoft Hyper-V Network Adapter #4 16 Up 00-15-5D-01-2A-92 10 Gbps 32 | Local Area Connection TAP-Windows Adapter V9 12 Disconnected 00-FF-B6-68-E1-5D 1 Gbps 33 | ``` 34 | 35 | Once you add a few NICs to a VM, each connected to a separate switch, telling them apart can be challenging. 36 | To help you with subsequent maintenance, it can be good to rename the adapter and change the description. 37 | Renaming a VM's NICs is a good habit to get into - and is straightforward to achieve. 38 | Before renaming anything, ensure you determine the purpose for each NIC in your VM. 39 | Once you work out what use each NIC plays in your VM farm, you can use the `Rename-NetAdapter` cmdlet to rename the NIC. 40 | 41 | There are two ways you could use `Rename-NetAdapter` to rename one of our NICs, like this: 42 | 43 | ```powershell 44 | # Using a 'Get-Rename' pattern 45 | Get-NetAdapter -InterfaceIndex 22 | Rename-NetAdapter -NewName 'Reskit Management' 46 | # Just Using Rename-NetAdapter 47 | Rename-NetAdapter -InterfaceIndex 22 -NewName 'Reskit Management' 48 | ``` 49 | 50 | I rarely, if ever, rename a NIC using a production script since it is usually a one-off operation. 51 | For that reason, I prefer to use the first method. 52 | I can first use `Get-NetAdapter` on its own to ensure I'm getting the right adapter. 53 | Then, I can hit `Up-Arrow`, and pipe the previous command to `Rename-NetAdapter` and specify a new name for the NIC. 54 | 55 | ## Admin rights required 56 | 57 | There is just one small snag with using `Rename-NetAdapter` - you have to run it in an elevated console. 58 | If, as I often do, forget to run PowerShell as an administrator, you would see the following when attempting to rename the NIC: 59 | 60 | ```powershell-console 61 | PS> Get-NetAdapter -InterfaceIndex 22 | Rename-NetAdapter -NewName 'Sales iSCSI VLAN' 62 | Rename-NetAdapter: Access is denied. 63 | ``` 64 | 65 | Although it might have been nice to tell you to run the command in an elevated PowerShell console, the error message should be clear enough. 66 | And, interestingly, this fact is not currently mentioned in the help text. 67 | 68 | Assuming that you are an administrator with the rights to change a NIC's name, you can open a new elevated PowerShell session and try the command again. 69 | If you are using PSReadLine, when you start up the new console (as an Administrator), the command should be in PSReadLine's command cache. 70 | And that means, once the new console is up and available, you can access that earlier command by hitting up-arrow and then hitting return. 71 | 72 | When you do, you see this: 73 | 74 | ```powershell-console 75 | PS> Get-NetAdapter -InterfaceIndex 16 | Rename-NetAdapter -NewName 'Sales iSCSI VLAN' 76 | PS> Get-NetAdapter -InterfaceIndex 16 77 | 78 | Name InterfaceDescription ifIndex Status MacAddress LinkSpeed 79 | ---- -------------------- ------- ------ ---------- --------- 80 | Sales iSCSI VLAN Microsoft Hyper-V Network Adapter 16 Up 00-15-5D-01-2A-91 10 Gbps 81 | ``` 82 | 83 | In this example: `Rename-NetAdapter` did change the name of the adapter but produced no console output. 84 | You use `Get-NetAdapter` to view the new name. 85 | 86 | ## There are other ways 87 | 88 | As ever with PowerShell, there are other ways you could change the name of a NIC. 89 | One more old-fashioned way would be to use the `netsh.exe` program. 90 | And then there is WMI - you can use the `Set-CimInstance` to perform the name change. 91 | And I look forward to comments suggesting other ways to change a NIC's name. 92 | 93 | ## Summary 94 | 95 | It is easy to change a network adapter's name. 96 | Unfortunately, the Rename-NetAdapter does not allow you to change the interface description. 97 | You need to run the `Rename-NetAdapter` in an elevated console - if you don't, you get an Access Denied error. 98 | 99 | ## Tip of the Hat 100 | 101 | I based this article on one written for the earlier Scripting Guys blog [Renaming Network Adapters by Using PowerShell](https://devblogs.microsoft.com/scripting/renaming-network-adapters-by-using-powershell/). 102 | The author of that article was Ed Wilson. 103 | -------------------------------------------------------------------------------- /Posts/2021/09/media/getaclad/ExtADPermission.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PowerShell/Community-Blog/7837d320a6bd85ca7d4469dcfaa115112333fac0/Posts/2021/09/media/getaclad/ExtADPermission.png -------------------------------------------------------------------------------- /Posts/2021/09/media/getaclad/InheritanceType_In_GUI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PowerShell/Community-Blog/7837d320a6bd85ca7d4469dcfaa115112333fac0/Posts/2021/09/media/getaclad/InheritanceType_In_GUI.png -------------------------------------------------------------------------------- /Posts/2021/09/media/getaclad/InheritedObjectType.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PowerShell/Community-Blog/7837d320a6bd85ca7d4469dcfaa115112333fac0/Posts/2021/09/media/getaclad/InheritedObjectType.png -------------------------------------------------------------------------------- /Posts/2021/09/media/getaclad/applythiscontainer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PowerShell/Community-Blog/7837d320a6bd85ca7d4469dcfaa115112333fac0/Posts/2021/09/media/getaclad/applythiscontainer.png -------------------------------------------------------------------------------- /Posts/2021/09/my-crescendo-journey.md: -------------------------------------------------------------------------------- 1 | --- 2 | Categories: PowerShell 3 | post_title: My Crescendo journey 4 | Summary: How I stopped worrying and learned to create a module 5 | tags: Crescendo, module 6 | username: sewhee@microsoft.com 7 | --- 8 | In a recent PowerShell Users Group meeting I was thinking that it might be good to talk about the 9 | new [Crescendo][blog1] module and how to use it. I was going to ask [Jason Helmick][jason] if he 10 | would do a presentation for us. Then, in an unrelated conversation, someone mentioned using 11 | `vssadmin.exe` for some project. This got me thinking: `vssadmin` is a perfect candidate for a 12 | Crescendo module and maybe I should just learn it and do the presentation myself. 13 | 14 | ## What is Crescendo? 15 | 16 | Crescendo is an experimental module developed by [Jim Truher][jim], one of the main developers of 17 | PowerShell. Crescendo provides a framework to rapidly develop PowerShell cmdlets that wrap native 18 | commands, regardless of platform. The goal of a Crescendo-based module is to create PowerShell 19 | cmdlets that use a native command-line tool, but unlike the tool, return PowerShell objects instead 20 | of plain text. 21 | 22 | ## How I got started 23 | 24 | When I first heard about Crescendo, I thought: 25 | 26 | > _So what. I've written wrapper modules like this before. How is this going to help me?_ 27 | 28 | But I knew there must be more to it for Jim to invest this much time and effort into it, and I 29 | wanted something to present at the user group meeting. 30 | 31 | So, I started by reading the blog posts about Crescendo and looking at some examples in the 32 | [repository][repo]. 33 | 34 | ### How Crescendo works 35 | 36 | The to create a module using the Crescendo framework you have to create two main components: 37 | 38 | - A JSON configuration file that describes the cmdlets you want 39 | - Output handler functions that parse the output from the native command and return objects 40 | 41 | Initially, the parsing code had to be embedded in the JSON file, which made writing and formatting 42 | the code very difficult. But, in the [Preview 3][blog3] release, Jim added the ability to create 43 | your output handler code in a function or a script file, making it much easier to manage. 44 | 45 | Alright! Writing the PowerShell functions is something I am more comfortable with, so that was my 46 | next step. 47 | 48 | ## Writing the output parser functions 49 | 50 | To create the parser functions I had to know what the output looked like for all of the possible 51 | command combinations of `vssadmin.exe`. I looked at the help provided by `vssadmin` and captured the 52 | output for each subcommand in a separate file. I used these output files to design and implement a 53 | parsing function for each subcommand. 54 | 55 | Now, on to the configuration file. 56 | 57 | ## Creating the JSON configuration 58 | 59 | For this I used the example from the [blog post][blog3] as a template. I also looked at the 60 | `Get-InstalledPackage` example from the [Preview 2][blog2] blog post to see how the native commands 61 | were referenced. For my first cmdlet I started with this JSON configuration: 62 | 63 | ```json 64 | { 65 | "$schema": "https://aka.ms/Crescendo/Schema.json", 66 | "Commands": [ 67 | { 68 | "Verb": "Get", 69 | "Noun": "VssProvider", 70 | "OriginalName": "$env:Windir/system32/vssadmin.exe", 71 | "OriginalCommandElements": [ 72 | "list", 73 | "providers" 74 | ], 75 | "OutputHandlers": [ 76 | { 77 | "ParameterSetName": "Default", 78 | "HandlerType": "Function", 79 | "Handler": "ParseProvider" 80 | } 81 | ], 82 | } 83 | ] 84 | } 85 | ``` 86 | 87 | The `ParseProvider` function is one of the functions that I had written to parse the output. I 88 | repeated this pattern to create a new cmdlet for each of the `vssadmin` subcommands. 89 | 90 | Notice that the first line of the JSON references a schema file. This file comes with the Crescendo 91 | module. I used Visual Studio Code (VS Code) to do all my development. With this schema file, VS Code 92 | provides IntelliSense for the JSON, making it easy to know which values are required and the type of 93 | information needed. 94 | 95 | Eventually, I added properties to the configuration for full help with descriptions and examples. 96 | And I defined parameter sets for the `vssadmin` commands that supported parameters. 97 | 98 | ## Creating the new module 99 | 100 | Crescendo, itself, is a module. It contains cmdlets that help you create your configuration and then 101 | uses that configuration to create the module containing your cmdlets. Once I was happy with the 102 | configuration file, I used the `Export-CrescendoModule` cmdlet to create my module. 103 | 104 | ```powershell 105 | Export-CrescendoModule -ConfigurationFile .\vssadmin.crescendo.config.json -ModuleName VssAdmin.psm1 106 | ``` 107 | 108 | Crescendo created two new files: 109 | 110 | - The module code file `VssAdmin.psm1` 111 | - The module manifest file `VssAdmin.psd1` 112 | 113 | These are the only two files that need to be installed. The `VssAdmin.psm1` file contains all the 114 | cmdlets that Crescendo generated from the configuration and the **Output Handler** functions I 115 | wrote to parse the output into objects. 116 | 117 | The end result was a well-structured, fully documented module. 118 | 119 | I still have one cmdlet left to create and I want to add administrative elevation since `vssadmin` 120 | requires it. But I am happy with the results I have so far. 121 | 122 | ## Conclusion 123 | 124 | After reading all of this you might still be asking "how is this any easier than just writing the 125 | module myself?" 126 | 127 | That is a fair question. But here are the conclusions I came to as I went through this process. 128 | 129 | - The whole process, starting from nothing, researching both Crescendo and `vssadmin`, writing the 130 | code, creating the configuration, and generating the module took me about 4 hours. I thought that 131 | was pretty fast. 132 | - Crescendo lets you separate the logic code (your parsing functions) from the cmdlet definition and 133 | parameter handling code. I found it easier to describe the cmdlets and their parameters in the 134 | JSON file rather than having to write that code myself. 135 | - Crescendo handles things like **CommonParameters** and `SupportsShouldProcess` for you. You don't 136 | have to write that support code in the cmdlets. 137 | - The configuration file also makes it easy to add help to your cmdlets. You don't have to remember 138 | the comment-based help keywords and structure. 139 | - Separating the declarative code (the JSON configuration) from the logical code (your parsers) 140 | makes it easier to add functionality to your module if the native command-line tool is updated. 141 | 142 | Take a few minutes to read the Crescendo blog posts. Then go and look at the VssAdmin module I 143 | created. I have included the link to it below. Examine the `vssadmin.crescendo.config.json` file to 144 | see how I defined the cmdlets and the parameter sets. The `vssadmin.exe resize shadowstorage` 145 | command has a `/MaxSize=` parameter that can take 3 different types of values. Look at the 146 | definition of the `Resize-VssShadowStorage` cmdlet to see how I handled that. 147 | 148 | ## Links to resources 149 | 150 | - The blog posts 151 | - [Announcing Crescendo Preview 1][blog1] 152 | - [Announcing Crescendo Preview 2][blog2] 153 | - [Announcing Crescendo Preview 3][blog3] 154 | - My [VssAdmin][vssadmin] module 155 | - The [Crescendo repository][repo] on GitHub 156 | - The [Microsoft.PowerShell.Crescendo][gallery] module on the PowerShell Gallery 157 | 158 | <!-- link references --> 159 | [blog1]: https://devblogs.microsoft.com/powershell/announcing-powershell-crescendo-preview-1/ 160 | [blog2]: https://devblogs.microsoft.com/powershell/announcing-powershell-crescendo-preview-2/ 161 | [blog3]: https://devblogs.microsoft.com/powershell/announcing-powershell-crescendo-preview-3/ 162 | [jim]: https://devblogs.microsoft.com/powershell/author/jimtrumicrosoft-com/ 163 | [jason]: https://devblogs.microsoft.com/powershell/author/jahelmic/ 164 | [repo]: https://github.com/PowerShell/Crescendo 165 | [vssadmin]: https://github.com/sdwheeler/modules/vssadmin 166 | [gallery]: https://www.powershellgallery.com/packages/Microsoft.PowerShell.Crescendo 167 | -------------------------------------------------------------------------------- /Posts/2021/09/tfl-profile.md: -------------------------------------------------------------------------------- 1 | --- 2 | post_title: How to Make Use Of PowerShell Profile Files 3 | username: tfl@psp.co.uk 4 | Categories: PowerShell 5 | tags: PowerShell, profile 6 | Summary: Using Profile files with PowerShell 7 7 | --- 8 | 9 | **Q:** I would like to personalize the way that PowerShell works. 10 | I have heard that I can use a thing called a profile to do this, but when I try to find information about profiles, I come up blank. There is no `New-Profile` cmdlet, so I do not see how to create such a thing. Can you help me, please? 11 | 12 | **A:** Profiles are a powerful part of PowerShell and allow you to customize PowerShell for your environment. 13 | They are easy to create and support a range of deployment scenarios. 14 | 15 | ## What is a Profile? 16 | 17 | Before explaining the profile, let's first examine the PowerShell host. 18 | A PowerShell host is a program that hosts PowerShell to allow you to use it. 19 | Common PowerShell hosts include the Windows PowerShell console, the Windows PowerShell ISE, the PowerShell 7 console, and VS Code. 20 | Each host supports the use of profile files. 21 | 22 | A profile is a PowerShell script file that a PowerShell host loads and executes automatically every time you start that PowerShell host. 23 | The script is, in effect, dot-sourced, so any variables, functions, and the like that you define in a profile script remain available in the PowerShell session, which is incredibly handy. 24 | I use profiles to create PowerShell drives, various useful variables, and a few useful (for me!) functions. 25 | 26 | Each PowerShell host has 4 separate profile files as follows: 27 | 28 | * This host, this user 29 | * This host, all users 30 | * All hosts, this user 31 | * All hosts, all users 32 | 33 | Why so many, you might ask. 34 | Because having these four profile files allows you numerous deployment opportunities. 35 | You could, for example, have one profile that defines corporate aliases or standard PS drives for every PowerShell host and user on a machine. 36 | You could have 'this host' profiles that define host-specific customizations that could differ depending on the PowerShell host. 37 | For example, in my profile file for VS code, I use `Set-PSReadLineOption` to set token colours depending on which color theme I am using. 38 | Like so many things in PowerShell, the PowerShell team engineered profiles for every scenario you might come across in deploying PowerShell and PowerShell hosts. 39 | 40 | In practice, the "this host, this user" profile is the one you most commonly use, but having all four allows considerable deployment flexibility. 41 | You have options! 42 | 43 | ## Where do I find them? 44 | 45 | Another frequently asked question is: where are these files and how are they named? 46 | It turns out, like many things PowerShell, you can find the answer to the question inside PowerShell itself. 47 | In this case, inside a PowerShell automatic variable, `$PROFILE`. 48 | 49 | Automatic variables in PowerShell, are variables created by PowerShell itself and are available for use. 50 | These variables are created by PowerShell when you start the host. 51 | For more details on automatic variables see the [automatic variable help text](https://docs.microsoft.com/powershell/module/microsoft.powershell.core/about/about_automatic_variables). 52 | 53 | ## The `$PROFILE` variable 54 | 55 | The `$PROFILE` variable is an automatic variable that PowerShell creates within each session during startup. 56 | This variable has both a **ToString()** method and four additional note properties that tell you where _this_ host finds its profile files. 57 | 58 | To determine the location and fill script name for the four PowerShell scripts, you can do something like this: 59 | 60 | ```powershell-console 61 | PS> # what host? 62 | PS> $host.Name 63 | ConsoleHost 64 | PS> # Where are the profiles? 65 | PS> $PROFILE | Get-Member -MemberType NoteProperty 66 | TypeName: System.String 67 | Name MemberType Definition 68 | ---- ---------- ---------- 69 | AllUsersAllHosts NoteProperty string AllUsersAllHosts=C:\\Program Files\\PowerShell\\7\\profile.ps1 70 | AllUsersCurrentHost NoteProperty string AllUsersCurrentHost=C:\\Program Files\\PowerShell\\7\\Microsoft.PowerShell_profile.ps1 71 | CurrentUserAllHosts NoteProperty string CurrentUserAllHosts=C:\\Users\doctordns\\Documents\\PowerShell\\profile.ps1 72 | CurrentUserCurrentHost NoteProperty string CurrentUserCurrentHost=C:\\Users\\doctordns\\Documents\\PowerShell\\Microsoft.PowerShell_profile.ps1 73 | 74 | PS> # What does the $PROFILE variable itself contain? 75 | PS> $PROFILE 76 | C:\\Users\\doctordns\\Documents\\PowerShell\\Microsoft.PowerShell_profile.ps1 77 | ``` 78 | 79 | This example is from a Windows 10 client running PowerShell 7 inside VS Code. 80 | In the example, you can see that the `$PROFILE` variable contains four note properties that contain the location of each profile 81 | Also, you can see that the `$PROFILE` variable's value is the name of the **CurrentUserCurrentHost** profile. 82 | For simplicity you can run `Notepad $Profile` to bring up the profile file inside Notepad (or use VS Code!) 83 | 84 | ## What can you do in a profile script? 85 | 86 | You can pretty much do anything you want in profile file to create the environment that works best for you. 87 | I find the profile useful for creating variables and short aliases, PS Drives, and more as you can see below. 88 | As an example of what you can do in a profile, and to get you started, I have published two sample profile files to GitHub: 89 | 90 | * A [profile for the PowerShell 7 console](https://github.com/doctordns/PACKT-PS7/blob/master/scripts/goodies/Microsoft.PowerShell_Profile.ps1) 91 | * A [profile for VSCode](https://github.com/doctordns/PACKT-PS7/blob/master/scripts/goodies/Microsoft.VSCode_profile.ps1) 92 | 93 | These samples do a lot of useful things, including: 94 | 95 | * Over-ride some default parameter values 96 | * Update the Format enumeration limit 97 | * Set the 'home' directory to a non-standard location 98 | * Create personal aliases 99 | * Create a PowerShell credential object 100 | 101 | These are all things that make the environment customized to your liking. 102 | I use some personal aliases as alternatives to standard aliases - if only to save typing. 103 | Creating personal variables or updating automatic variables can be useful. 104 | 105 | While creating a credential object can be useful, it is arguable whether it is a good thing. 106 | In this case, the credential is for a set of VMs I used in my [most recent PowerShell book](https://smile.amazon.co.uk/Windows-Server-Automation-PowerShell-Cookbook-ebook/dp/B0977JDL7K/ref=sr_1_1?dchild=1&keywords=Windows+Server+Automation+with+PowerShell+Cookbook+-+Fourth+Edition&qid=1624277697&s=books&sr=1-1) to illustrate using PowerShell in an Enterprise. 107 | As they are all local VMs and are only for testing, creating a much used credential object is useful. 108 | 109 | ## Be Careful 110 | 111 | It is easy to get carried away with profile files. 112 | At one point in the PowerShell 3.0 days, my profile file was over 700 lines long. 113 | I'd just chucked all these cool things I'd found on the Internet (and never used them again) 114 | As a result, starting PowerShell or the ISE took some time. 115 | It is so easy to see some cool bits of code and then add it to your profile. 116 | I suggest you look carefully at each profile on a regular basis and trim it when possible. 117 | 118 | ## Summary 119 | 120 | Profile are PowerShell scripts you can use to customize your PowerShell environment. 121 | There are 4 profile files for each host as you can see by examining the `$Profile` automatic variable. 122 | 123 | ## Tip of the Hat 124 | 125 | I based this article on one written for the earlier Scripting Guys blog [How Can I Use Profiles With Windows PowerShell](https://devblogs.microsoft.com/scripting/hey-scripting-guy-how-can-i-use-profiles-with-windows-powershell/). 126 | It was written by Ed Wilson. 127 | -------------------------------------------------------------------------------- /Posts/2021/10/Expresso.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PowerShell/Community-Blog/7837d320a6bd85ca7d4469dcfaa115112333fac0/Posts/2021/10/Expresso.png -------------------------------------------------------------------------------- /Posts/2021/10/crescendo-output-handler.md: -------------------------------------------------------------------------------- 1 | --- 2 | Categories: PowerShell 3 | post_title: A closer look at the parsing code of a Crescendo output handler 4 | Summary: In this post I take a close look at one of my output handler and talk about the different parsing methods I used. 5 | tags: Crescendo, output handler, parsing 6 | username: sewhee@microsoft.com 7 | --- 8 | In my [previous post][2], I showed you how to parse the output from the `netstat` command. The 9 | output of `netstat` is not very complex. The goal of the post was to introduce some parsing 10 | strategies that you can use to create a full Crescendo module. In this post, I explain the details 11 | of a more complex parsing function that I created for my [VssAdmin module][7]. 12 | 13 | ## Examining the parser for `Get-VssShadowStorage` 14 | 15 | The following screenshot shows the `ParseShadowStorage` function that is called by the 16 | `Get-VssShadowStorage` cmdlet to handle the output from `vssadmin.exe`. The parsing process is 17 | broken into four areas: 18 | 19 | - Collect the native command output into a single text blob then split it into blocks of text (lines 20 | 96-100) 21 | - Parse each line of a text block (lines 105-132) 22 | - Collect the parsed data in a hashtable (lines 104, 119, and 129) 23 | - Return the collected data as an object (line 133) 24 | 25 | ![ParseShadowStorage function](crescendo-parser.png) 26 | 27 | ### Collecting the native command output 28 | 29 | The output from `vssadmin.exe list shadowstorage` is passed into the output handler function as an 30 | array of strings. Here is an example of the output: 31 | 32 | ``` 33 | vssadmin 1.1 - Volume Shadow Copy Service administrative command-line tool 34 | (C) Copyright 2001-2013 Microsoft Corp. 35 | 36 | Shadow Copy Storage association 37 | For volume: (C:)\\\\?\\Volume{67a44989-8413-4a7c-a616-79385dae8605}\\ 38 | Shadow Copy Storage volume: (C:)\\\\?\\Volume{67a44989-8413-4a7c-a616-79385dae8605}\\ 39 | Used Shadow Copy Storage space: 22.1 GB (4%) 40 | Allocated Shadow Copy Storage space: 22.5 GB (4%) 41 | Maximum Shadow Copy Storage space: 23.7 GB (5%) 42 | ``` 43 | 44 | All the `vssadmin.exe` commands followed this pattern. There is a 2-line header followed by one or 45 | more groups of lines of data. Each group of text is separated by a blank line. So first, I wanted to 46 | get the text split into the blocks separated by the blank line. 47 | 48 | Piping the `$cmdresults` parameter to `Out-String` turns that array of lines into one contiguous 49 | blob of text. Then, I split that blob into blocks of text at the blank line using the 50 | ``-split "`r`n`r`n"`` operator. The `$textblocks` variable now contains 2 block of text. The first 51 | block is the header and the second block is the data. Using the `for` loop on line 102 I start 52 | proccessing the lines in the second text block. I can skip the first since is only contains the 53 | header. 54 | 55 | ### Parsing the lines of the textblocks 56 | 57 | On line 105, I split the text block into an array of lines so that I can process each line. Looking 58 | at the example data above, I can see that there are two kinds of information: _volume_ and _space_ 59 | information. 60 | 61 | Since both information sets have the same basic format, I only need one parser for each type of 62 | information. Beginning on line 107, the `foreach` loop iterates over each line of the text block. On 63 | line 108, the `switch` statement uses regular expression matching (regex) to determine whether the 64 | line contains _volume_ or _space_ information. Lines 109-120 parse the volume information. Lines 65 | 121-130 parse the space information. 66 | 67 | In both cases, I want to collect the data in a key/value pair. The line is split into two parts 68 | by the **"volume:"** or **"space:"** strings. 69 | 70 | The text of the first part is used to create the key name. 71 | 72 | - For volume information, I remove the spaces from the text to form the key names: **ForVolume** and 73 | **ShadowCopyStorageVolume**. 74 | - For space information, I split out the first word to form the key names: **UsedSpace**, 75 | **AllocatedSpace**, and **MaximumSpace**. 76 | 77 | The second part contains the value data. Each information type contains two data values: 78 | 79 | - For volume information: the volume name and path 80 | 81 | In line 113, I use a [regex with named groups](#expresso) to isolate the volume name and path from 82 | the string data. Those values are assigned to properties of the `$volinfo` object, which becomes 83 | the value for the key/value pair. 84 | 85 | - For space information: the size and percent usage 86 | 87 | To get to the space data items, I split the string at the open parenthesis `(` and trim off the 88 | closing parenthesis. The first part becomes the size value and the second part becomes the 89 | percentage. Similar to the volume data, these values are assigned to properties of the `$space` 90 | object, which becomes the value of the key/value pair. 91 | 92 | ### Returning the object 93 | 94 | I now have a key/value pair ready to be added to a hashtable in line 119 or 129. Once all the lines 95 | have been parsed, the hashtable is complete. Line 133 converts the hashtable to a **PSObject**, 96 | which is returned to the `Get-VssShadowStorage` cmdlet function for output. 97 | 98 | ## Conclusion 99 | 100 | I use this same strategy to create parsing functions for all of the `vssadmin` command outputs. 101 | 102 | - Collect the native command output as a single text blob 103 | - Split the blob into separate text blocks 104 | - Parse each line of a text block to extract the data 105 | - Collecting the parsed data in a hashtable 106 | - Return the collected data as an object 107 | 108 | I used several different methods to parse the strings into data: 109 | 110 | - String class methods (`Split()`, `Trim()`, `Replace()`) 111 | - PowerShell Operators (`-split` and `-match`) 112 | - Regular expressions (with `-match` and `switch`) 113 | 114 | Each native command has its own unique output. Crescendo does not help you analyze or parse that 115 | output. You must take the time to collect the output, analyze the structure of the information, and 116 | devise a strategy for extracting the data from the strings. 117 | 118 | Crescendo separates the structural code required to create a cmdlet from the functional code that 119 | extracts the data. In my next post, I will take a close look at the JSON configuration file that 120 | Crescendo uses to define the structural code. 121 | 122 | ## Resources 123 | 124 | Posts in this series 125 | 126 | - [My Crescendo journey][1] 127 | - [My VssAdmin module][7] 128 | - [Converting string output to objects][2] 129 | - A closer look at a Crescendo Output Handler - this post 130 | - A closer look at a Crescendo configuration file - coming soon 131 | 132 | References 133 | 134 | - [about_Regular_Expressions][3] 135 | - [about_Operators][4] 136 | - [about_Switch][5] 137 | - [Expresso by Ultrapico][6] - my favorite (and free) regular expression tool 138 | 139 | <a id='expresso'>![expresso](Expresso.png)</a> 140 | 141 | <!-- link reference --> 142 | [1]: https://devblogs.microsoft.com/powershell-community/my-crescendo-journey/ 143 | [2]: https://devblogs.microsoft.com/powershell-community/converting-string-output-to-objects/ 144 | [3]: https://docs.microsoft.com/powershell/module/microsoft.powershell.core/about/about_regular_expressions 145 | [4]: https://docs.microsoft.com/powershell/module/microsoft.powershell.core/about/about_operators 146 | [5]: https://docs.microsoft.com/powershell/module/microsoft.powershell.core/about/about_switch 147 | [6]: http://www.ultrapico.com/Expresso.htm 148 | [7]: https://github.com/sdwheeler/tools-by-sean/tree/master/modules/vssadmin 149 | -------------------------------------------------------------------------------- /Posts/2021/10/crescendo-parser.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PowerShell/Community-Blog/7837d320a6bd85ca7d4469dcfaa115112333fac0/Posts/2021/10/crescendo-parser.png -------------------------------------------------------------------------------- /Posts/2021/10/netstat-output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PowerShell/Community-Blog/7837d320a6bd85ca7d4469dcfaa115112333fac0/Posts/2021/10/netstat-output.png -------------------------------------------------------------------------------- /Posts/2021/11/tfl-formatenumeration.md: -------------------------------------------------------------------------------- 1 | --- 2 | post_title: How to Use $FormatEnumerationLimit 3 | username: tfl@psp.co.uk 4 | Categories: PowerShell 5 | tags: PowerShell, Format, FormatEnumerationLimit variable 6 | Summary: Using The $FormatEnumerationLimit variable in PowerShell 7 | --- 8 | 9 | **Q:** When I format an object where a property contains more than 4 objects, I never see the extra property values. How can I fix that? 10 | 11 | **A:** Use the `$FormatEnumerationLimit` variable. 12 | 13 | This query is one I hear in many PowerShell support forums, and I have encountered this issue a lot over the years. 14 | What happens is that you issue a command to return objects, for example `Get-Process`. 15 | The `Get-*` cmdlets return objects which can contain properties that are arrays of values, not just a single value. 16 | When you pipe those objects to `Format-Table`, by default, PowerShell only shows you the first four. 17 | 18 | Let me illustrate what this looks like (by default): 19 | 20 | ```powershell-console 21 | PS> Get-Process -Name pwsh | Format-Table -Property ProcessName, Modules 22 | 23 | ProcessName Modules 24 | ----------- ------- 25 | pwsh {System.Diagnostics.ProcessModule (pwsh.exe), 26 | System.Diagnostics.ProcessModule (ntdll.dll), 27 | System.Diagnostics.ProcessModule (KERNEL32.DLL), 28 | System.Diagnostics.ProcessModule (KERNELBASE.dll)…} 29 | ``` 30 | 31 | This output shows PowerShell getting the process object for `Pwsh.exe` and then passing it to `Format-Table`, which outputs the process name and the modules used by that process. 32 | However, as you can see, PowerShell shows only four modules shown followed by "…" (also known as an ellipsis). 33 | The ellipsis tells you that there are more values in this property, except PowerShell does not show them. 34 | 35 | If you know the `Format-Table` command, you might be tempted to use the `-Wrap` or the `-AutoSize` parameters, but these would not help. 36 | It turns out there is no parameter for `Format-Table` or `Format-List` to control this. 37 | The trick is to use the `$FormatEnumerationLimit` variable and assign it a higher value. 38 | 39 | The `$FormatEnumerationLimit` automatic variable tells PowerShell and the formatting cmdlets how many occurrences to include in the formatted output. 40 | By default, PowerShell sets this variable to four at startup. 41 | And that is why you see just four processes in output (by default). 42 | 43 | With PowerShell, you can adjust this limit in a script or a profile file. 44 | When you change the value, PowerShell outputs more occurrences, up to the limit you set in `$FormatEnumerationLimit`. 45 | Like this: 46 | 47 | ```powershell-console 48 | PS > $FormatEnumerationLimit = 8 49 | PS > Get-Process -Name PWSH | Format-Table -Property ProcessName, Modules 50 | 51 | ProcessName Modules 52 | ----------- ------- 53 | pwsh {System.Diagnostics.ProcessModule (pwsh.exe), 54 | System.Diagnostics.ProcessModule (ntdll.dll), 55 | System.Diagnostics.ProcessModule (KERNEL32.DLL), 56 | System.Diagnostics.ProcessModule (KERNELBASE.dll), 57 | System.Diagnostics.ProcessModule (apphelp.dll), 58 | System.Diagnostics.ProcessModule (USER32.dll), 59 | System.Diagnostics.ProcessModule (win32u.dll), 60 | System.Diagnostics.ProcessModule (GDI32.dll)…} 61 | ``` 62 | 63 | In the above output, you can see output for eight modules. 64 | In writing this, there are actually 239 actual modules for the PowerShell process. 65 | If you need to see all the modules, you could set `$FormatEnumerationLimit` to a larger number (e.g. 999) in the shell. 66 | Alternatively, if you set `$FormatEnumerationLimit` to -1, PowerShell displays all occurrences, which may be more than you want in most cases! 67 | I set the limit to 99 in my profile file and that is usually more than sufficient. 68 | 69 | ## Scoping of $FormatEnumerationLimit 70 | 71 | One interesting thing I found is that `$FormatEnumerationLimit` is scoped differently to my expectations. 72 | If you use a format command within a function or script (a child of the global scope), the command only uses the value from the global scope. 73 | 74 | The following code contains a function to illustrate the issue: 75 | 76 | ```powershell 77 | function Test-FormatLimitLocal 78 | { 79 | # Change format enum limit 80 | "In Function, limit is: [$FormatEnumerationLimit]" 81 | $FormatEnumerationLimit = 1 82 | "After changing: [$FormatEnumerationLimit]" 83 | Get-Process | Select-Object -Property Name, Threads -First 4 84 | } 85 | ``` 86 | 87 | You might think that this code would display the first thread in each of the first four processes. 88 | You might, but you would be wrong, as you can see here: 89 | 90 | ```powershell-console 91 | PS> # Here show the value and call the functin 92 | PS> "Before calling: [$FormatEnumerationLimit]" 93 | Before calling: [4] 94 | PS> Test-FormatLimitLocal 95 | In Function, limit is: [4] 96 | After changing: [1] 97 | 98 | Name Threads 99 | ---- ------- 100 | AggregatorHost {5240} 101 | ApplicationFrameHost {16968, 2848} 102 | AppVShNotify {9164} 103 | Atom.SDK.WindowsService {4064, 4908, 4912, 19144…} 104 | ``` 105 | 106 | As you can see from this output, the final process shows FOUR threads not ONE. 107 | This is because PowerShell seems to only use the globally scoped value, not the locally scoped copy. 108 | To get around this curious scoping, you can re-write the function like this: 109 | 110 | ```powershell 111 | 112 | function Test-FormatLimitGlobal 113 | { 114 | # Change format enum limit Globally 115 | $Old = $Global:FormatEnumerationLimit 116 | $Global:FormatEnumerationLimit = 1 117 | "After changing: [$Global:FormatEnumerationLimit]" 118 | Get-Process | Select-Object -Property Name, Threads -First 4 119 | # Change it back 120 | $Global:FormatEnumerationLimit = $Old 121 | } 122 | ``` 123 | 124 | When you call the updated function, it now operates more as you might wish, like this: 125 | 126 | ```powershell-console 127 | PS> # View the value 128 | PS> "Before calling: [$FormatEnumerationLimit]" 129 | Before calling: [4]# 130 | PS> # Now call the updated function 131 | PS> Test-FormatLimitGlobal 132 | After changing: [1] 133 | 134 | Name Threads 135 | ---- ------- 136 | AggregatorHost {5240} 137 | ApplicationFrameHost {16968…} 138 | AppVShNotify {9164} 139 | Atom.SDK.WindowsService {4064…} 140 | ``` 141 | 142 | So, with some careful updating of the global variable, you can get the desired result. 143 | In general, I teach my students to avoid manipulating global variables from within a script or a function (unless you know what you are doing). 144 | If you need to make changes to any global variable to make a function or script do what you want, ensure you know how to revert the variable to its original value. 145 | 146 | I am unclear whether this is a bug or a feature! To that end, I submitted a [feature request](https://github.com/PowerShell/PowerShell/issues/16360) in the PowerShell source repository. Feel free to add your opinion in the comments or upvote it if you want to see it added. 147 | 148 | ## Summary 149 | 150 | The `$FormatEnumerationLimit` variable is a neat feature of PowerShell that allows you to see more occurrences when using `Format-Table`. 151 | But remember: if you are using this variable in a function or a script, you should be aware of the scoping issue. 152 | 153 | You can read more about `$FormatEnumerationLimit`, and other preference variables in [about_Preference_Variables](https://docs.microsoft.com/powershell/module/microsoft.powershell.core/about/about_preference_variables#formatenumerationlimit). 154 | -------------------------------------------------------------------------------- /Posts/2021/12/jon-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PowerShell/Community-Blog/7837d320a6bd85ca7d4469dcfaa115112333fac0/Posts/2021/12/jon-1.png -------------------------------------------------------------------------------- /Posts/2021/12/jon-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PowerShell/Community-Blog/7837d320a6bd85ca7d4469dcfaa115112333fac0/Posts/2021/12/jon-2.png -------------------------------------------------------------------------------- /Posts/2021/12/media/tfl-preview/after.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PowerShell/Community-Blog/7837d320a6bd85ca7d4469dcfaa115112333fac0/Posts/2021/12/media/tfl-preview/after.png -------------------------------------------------------------------------------- /Posts/2021/12/media/tfl-preview/before.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PowerShell/Community-Blog/7837d320a6bd85ca7d4469dcfaa115112333fac0/Posts/2021/12/media/tfl-preview/before.png -------------------------------------------------------------------------------- /Posts/2021/12/tfl-params.md: -------------------------------------------------------------------------------- 1 | --- 2 | post_title: How to Use $PSDefaultParameterValues 3 | username: tfl@psp.co.uk 4 | Categories: PowerShell 5 | tags: PowerShell, Default Parameter values, parameters 6 | Summary: Using The $PSDefaultParameterValues automatic variable 7 | --- 8 | 9 | **Q:** When I use cmdlets like `Receive-Job` and `Format-Table`, how do I change default values of the **Keep** and **Wrap** parameters? 10 | 11 | **A:** Use the `$PSDefaultValues` automatic variable. 12 | 13 | When I first discovered PowerShell's background jobs feature, I would use `Receive-Job` to view job output - only to discover it's no longer there. 14 | And almost too often to count, I pipe objects to `Format-Table` cmdlet only to get truncated output because I forgot to use `-Wrap`. 15 | I'm sure you all have parameters whose default value you would gladly change - at least for your environment. 16 | 17 | I'm sure you have seen this (and know how to use **Wrap**), like this: 18 | 19 | ```powershell-console 20 | PS> # Default output in a narrow terminal window. 21 | PS> Get-Service | Format-Table -Property Name, Status, Description 22 | 23 | Name Status Description 24 | ---- ------ ----------- 25 | AarSvc_f88db Running Runtime for activating conversational … 26 | AJRouter Stopped Routes AllJoyn messages for the local … 27 | ALG Stopped Provides support for 3rd party protoco… 28 | AppHostSvc Running Provides administrative services for I… 29 | ... 30 | PS > # Versus this using -Wrap 31 | PS > Get-Service | Format-Table -Property Name, Status, Description -Wrap 32 | 33 | Name Status Description 34 | ---- ------ ----------- 35 | AarSvc_f88db Running Runtime for activating conversational agent 36 | applications 37 | AJRouter Stopped Routes AllJoyn messages for the local 38 | AllJoyn clients. If this service is stopped 39 | the AllJoyn clients that do not have their 40 | own bundled routers will be unable to run. 41 | ALG Stopped Provides support for 3rd party protocol 42 | plug-ins for Internet Connection Sharing 43 | AppHostSvc Running Provides administrative services for IIS, 44 | for example configuration history and 45 | Application Pool account mapping. If this 46 | service is stopped, configuration history 47 | and locking down files or directories with 48 | Application Pool specific Access Control 49 | Entries will not work. 50 | 51 | ``` 52 | 53 | So, the question is: how to tell PowerShell to always use `-Wrap` when using `Format-Table` or `Format-List`? 54 | It turns out there is a very simple way: use the `$PSDefaultParameters` automatic variable. 55 | 56 | ## The `$PSDefaultParameters` automatic variable 57 | 58 | When PowerShell (and Windows PowerShell) starts, it creates the `$PSDefaultParameters` automatic variable. 59 | The variable has a type: **System.Management.Automation.DefaultParameterDictionary**. 60 | In other words, the variable is a Powershell hash table. 61 | By default, the variable is empty when you start PowerShell. 62 | 63 | Each entry in this hash table defines a cmdlet, a parameter and a default value for that parameter. 64 | The hash table key is the name of the cmdlet, followed by a colon (`:`), and then the name of the parameter. 65 | The hash table value for this key is the new default value for the parameter. 66 | 67 | If you wanted, for example, to always use **-Wrap** for the `Format-*` cmdlets, you could do this: 68 | 69 | ```PowerShell 70 | $PSDefaultParameterValues.Add('Format-*:Wrap', $True) 71 | ``` 72 | ## Persist the change in your profile 73 | Any change you make to the `$PSDefaultParameterValues` variable is only applicable for the current session. 74 | And the variable is subject to normal scoping rules - so changing the value in a script does not affect the session as a whole. 75 | That means that if you want these changes to occur every time you start a PowerShell console, then you add the appropriate statements in your profile. 76 | 77 | On my development box, I use the following snippet inside my `$PROFILE` script: 78 | 79 | ```powerShell 80 | $PSDefaultParameterValues.Add('Format-*:AutoSize', $true) 81 | $PSDefaultParameterValues.Add('Format-*:Wrap', $true) 82 | $PSDefaultParameterValues.Add('Receive-Job:Keep', $true) 83 | ``` 84 | 85 | ## Summary 86 | 87 | The `$PSDefaultParameterValues` automatic variable is a great tool to help you specify specific values for cmdlet parameters. 88 | You can specify one or more cmdlets by using wild cards in the hash table's key. 89 | Remember that the hash table key is the name of the cmdlet(s), a colon, and then the parameter's name. 90 | Also, the hash table value is the new "default" value for that parameter (and for the specified cmdlet(s)). 91 | 92 | You can read more about `$PSDefaultParameterValues`, and other preference variables in [about_Preference_Variables](https://docs.microsoft.com/powershell/module/microsoft.powershell.core/about/about_preference_variables#psdefaultparametervalues). 93 | And for more details of parameter default values, see the [about_Parameters_Default_Values help file](https://docs.microsoft.com/powershell/module/microsoft.powershell.core/about/about_parameters_default_values). 94 | 95 | -------------------------------------------------------------------------------- /Posts/2021/12/tfl-preview.md: -------------------------------------------------------------------------------- 1 | --- 2 | post_title: How to Preview PowerShell Scripts In PowerShell 3 | username: tfl@psp.co.uk 4 | Categories: PowerShell 5 | tags: PowerShell, Explorer, Explorer Preview 6 | Summary: How to Preview PowerShell .PS1, .PSD1, .PSD1 files inside Windows Explorer. 7 | --- 8 | 9 | **Q:** When I use Windows Explorer and select a PowerShell script file - I do not see the script in the preview window. Can I fix that? 10 | 11 | **A:** You can make a few simple registry updates and do just what you want! 12 | 13 | At some time in the deep and distant past, Windows Explorer gained the preview pane feature. 14 | The idea is simple: you select a file in Explorer and Windows shows you a preview of the file in a separate pane. 15 | I love this feature, although when clicking on a Word document, it could take a few moments before I could view. 16 | And if you are viewing a file, it was "open" and you could not delete it in a separate window! 17 | A great feature albeit with some minor side effects - so it makes sense that this is turned off by default. 18 | But you can easily turn it back on! 19 | 20 | ## Microsoft PowerToys to the rescue?? 21 | 22 | You can use [Microsoft's Power Toys for Windows](https://docs.microsoft.com/windows/powertoys) to enable Explorer to preview more file types. 23 | I love these tools - and have them installed on my computers. 24 | Sadly, PowerToys currently does not enable previewing of PowerShell files. 25 | 26 | ## Enabling Preview in Windows Explorer 27 | 28 | As I mentioned above, file preview within Windows Explorer is disabled by default. 29 | To turn this on, use Explorer's View menu and select preview. 30 | I leave the details of how to set this up as an exercise for the user. 31 | As a small aside, this setting gets reset each time you upgrade Windows - as a Windows Insider, I have to reset this with each new build I take. :-( 32 | 33 | ## Enabling Preview of .PS1/.PSD1/.PSM1 files 34 | 35 | Once you enable preview mode in Explorer as shown above, when you select a `.PS1` file - you see something like this: 36 | 37 | ![Viewing a .PS1 file in Preview](./media/tfl-preview/before.png) 38 | 39 | There is currently no mechanism in Explorer to change the list of file types to be displayed. 40 | Fortunately, there is a straightforward mechanism that involves setting a registry key value. 41 | To enable Explorer to display the relevant files, you can use the following script fragment: 42 | 43 | ```powershell 44 | # Set path variables for PowerShell file types 45 | $Path1 = 'Registry::HKEY_CLASSES_ROOT\\.ps1' 46 | $Path2 = 'Registry::HKEY_CLASSES_ROOT\\.psm1' 47 | $Path3 = 'Registry::HKEY_CLASSES_ROOT\\.psd1' 48 | 49 | # Enable preview of those file types 50 | New-ItemProperty -Path $Path1 -Name PerceivedType -PropertyType String -Value 'text' 51 | New-ItemProperty -Path $Path2 -Name PerceivedType -PropertyType String -Value 'text' 52 | New-ItemProperty -Path $Path3 -Name PerceivedType -PropertyType String -Value 'text' 53 | ``` 54 | 55 | ## Result! 56 | 57 | Once you run this script, Explorer displays the script file, Explorer now looks like this: 58 | 59 | ![Viewing a .PS1 file in Preview after updating the registry](./media/tfl-preview/after.png) 60 | 61 | That's it - a small change to the registry and I can now preview PowerShell files. 62 | Very handy! 63 | -------------------------------------------------------------------------------- /Posts/2022/07/explorer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PowerShell/Community-Blog/7837d320a6bd85ca7d4469dcfaa115112333fac0/Posts/2022/07/explorer.png -------------------------------------------------------------------------------- /Posts/2022/07/format-list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PowerShell/Community-Blog/7837d320a6bd85ca7d4469dcfaa115112333fac0/Posts/2022/07/format-list.png -------------------------------------------------------------------------------- /Posts/2022/07/format-table.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PowerShell/Community-Blog/7837d320a6bd85ca7d4469dcfaa115112333fac0/Posts/2022/07/format-table.png -------------------------------------------------------------------------------- /Posts/2022/07/prompt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PowerShell/Community-Blog/7837d320a6bd85ca7d4469dcfaa115112333fac0/Posts/2022/07/prompt.png -------------------------------------------------------------------------------- /Posts/2022/07/tab-completion.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PowerShell/Community-Blog/7837d320a6bd85ca7d4469dcfaa115112333fac0/Posts/2022/07/tab-completion.png -------------------------------------------------------------------------------- /Posts/2022/08/Get-CimInstance_autoComplete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PowerShell/Community-Blog/7837d320a6bd85ca7d4469dcfaa115112333fac0/Posts/2022/08/Get-CimInstance_autoComplete.png -------------------------------------------------------------------------------- /Posts/2022/08/Invoke-CimMethod_autoComplete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PowerShell/Community-Blog/7837d320a6bd85ca7d4469dcfaa115112333fac0/Posts/2022/08/Invoke-CimMethod_autoComplete.png -------------------------------------------------------------------------------- /Posts/2022/08/NamespaceManiputlation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PowerShell/Community-Blog/7837d320a6bd85ca7d4469dcfaa115112333fac0/Posts/2022/08/NamespaceManiputlation.png -------------------------------------------------------------------------------- /Posts/2023/03/Update-XML-File-using-PowerShell.md: -------------------------------------------------------------------------------- 1 | --- 2 | post_title: Update XML files using PowerShell 3 | username: sorastog 4 | categories: PowerShell 5 | tags: PowerShell, XML, Configuration 6 | summary: This posts explains how to update XML files using PowerShell 7 | --- 8 | 9 | There are many blogs on internet already speaking about updating XML files in PowerShell, but I 10 | felt need of one consolidated blog where complex XML files can also be updated with long complex 11 | hierarchy of XML nodes and attributes. 12 | 13 | Below is an XML example which we will try in this blog to update at various level of node hierarchy. 14 | 15 | ## Sample Code 16 | 17 | ```xml 18 | <?xml version="1.0" encoding="utf-8"?> 19 | <Data version="2.0"> 20 | <Roles> 21 | <Role Name="ManagementServer" Value="OldManagementServer" /> 22 | </Roles> 23 | <SQL> 24 | <Instance Server="OldSQLServer" Instance="MSSQLSERVER" Version="SQL Server 2012"> 25 | <Variable Name="SQLAdmin" Value="Domain\OldSQlAdmin" /> 26 | <Variable Name="SQLUser" Value="domain\sqluser" /> 27 | </Instance> 28 | </SQL> 29 | <VMs> 30 | <VM Type="ClientVM"> 31 | <VMName>ClientVM</VMName> 32 | </VM> 33 | <VM Type="DNSServerVM"> 34 | <VMName>OldDNSServer</VMName> 35 | </VM> 36 | </VMs> 37 | </Data> 38 | ``` 39 | 40 | ## Steps to follow 41 | 42 | We will update the nodes in this XML file to use a new management, SQL, and DNS servers. Below are 43 | the steps given separately on how we can update the nodes and their attributes at various levels. 44 | 45 | 1. Define the variables which need to be modified: 46 | 47 | ```powershell 48 | $path = 'C:\Users\sorastog\Desktop\blog\Variable.xml' 49 | $ManagementServer = 'NewManagementServer' 50 | $SQLServer = 'NewSQLServer' 51 | $SQLAdmin = 'Domain\NewSQlAdmin' 52 | $DNSServerVMName = 'NewDNSServer' 53 | ``` 54 | 55 | 1. Reading the content of XML file. 56 | 57 | ```powershell 58 | $xml = [xml](Get-Content -Path $path) 59 | ``` 60 | 61 | 1. Update `ManagementServer`: Change the attribute **Value** of nodes at level 3 based on the 62 | **Name** attribute on the same level. 63 | 64 | ```powershell 65 | $node = $xml.Data.Roles.Role | 66 | Where-Object -Process { $_.Name -eq 'ManagementServer' } 67 | $node.Value = $ManagementServer 68 | ``` 69 | 70 | 1. Update `SQLServer`: Change the attribute **Value** of a node at level 3. 71 | 72 | ```powershell 73 | $node = $xml.Data.SQL.Instance 74 | $node.Server = $SQLServer 75 | ``` 76 | 77 | 1. Update `SQLAdmin`: Change the attribute **Value** of nodes at level 4 based on the **Name** 78 | attribute on the same level. 79 | 80 | ```powershell 81 | $node = $xml.Data.SQL.Instance.Variable | 82 | Where-Object -Process { $_.Name -eq 'SQLAdmin' } 83 | $node.Value = $SQLAdmin 84 | ``` 85 | 86 | 1. Update `DNSServerVM`: Change the attribute **Value** of nodes at level 4 based on the **VMType** 87 | attribute at the level above. 88 | 89 | ```powershell 90 | $node = $xml.Data.VMs.VM | 91 | Where-Object -Process { $_.Type -eq 'DNSServerVM' } 92 | $node.VMName = $DNSServerVMName 93 | ``` 94 | 95 | 1. Save changes to the XML file. 96 | 97 | ```powershell 98 | $xml.Save($path) 99 | ``` 100 | 101 | ## Output 102 | 103 | The final PowerShell script would look like below: 104 | 105 | ```powershell 106 | $path = 'C:\Data.xml' 107 | $ManagementServer = 'NewManagementServer' 108 | $SQLServer = 'NewSQLServer' 109 | $SQLAdmin = 'Domain\NewSQlAdmin' 110 | $DNSServerVMName = 'NewDNSServer' 111 | 112 | $xml = [xml](Get-Content $path) 113 | 114 | $node = $xml.Data.Roles.Role | 115 | Where-Object -Process { $_.Name -eq 'ManagementServer' } 116 | $node.Value = $ManagementServer 117 | 118 | $node = $xml.Data.SQL.Instance 119 | $node.Server = $SQLServer 120 | 121 | $node = $xml.Data.SQL.Instance.Variable | 122 | Where-Object -Process { $_.Name -eq 'SQLAdmin' } 123 | $node.Value = $SQLAdmin 124 | 125 | $node = $xml.Data.VMs.VM | 126 | Where-Object -Process { $_.Type -eq 'DNSServerVM' } 127 | $node.VMName = $DNSServerVMName 128 | 129 | $xml.Save($path) 130 | ``` 131 | 132 | Hope this will help you to update even complex XML files with multiple nodes and deep hierarchies. 133 | If there are some XML nodes that you would like to update and the category is not included in this 134 | blog, please reply to this post and I will add it. 135 | 136 | Till Then, Happy Scripting :) 137 | -------------------------------------------------------------------------------- /Posts/2023/04/media/Convert-Specific-Sheet-Of-Excel-Into-Json-Using-PowerShell/Image-MultipleTablesInOneSheet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PowerShell/Community-Blog/7837d320a6bd85ca7d4469dcfaa115112333fac0/Posts/2023/04/media/Convert-Specific-Sheet-Of-Excel-Into-Json-Using-PowerShell/Image-MultipleTablesInOneSheet.png -------------------------------------------------------------------------------- /Posts/2023/05/Designing-For-User-Experience-In-PowerShell.md: -------------------------------------------------------------------------------- 1 | --- 2 | post_title: Designing PowerShell For End Users 3 | username: svalding 4 | categories: PowerShell 5 | tags: PowerShell, Design, Toolmaking, User Experience 6 | summary: This posts explains taking user experience into account when designing PowerShell tools 7 | --- 8 | 9 | PowerShell, being built on .NET and object-oriented in nature, is a _fantastic_ language for developing 10 | tooling that you can deliver to your end users. These may be fellow technologists, or they could also be 11 | non-technical users within your organization. This could also be a tool you wish to share with the community, 12 | either via your own Github or by publishing to the PowerShell Gallery. 13 | 14 | ## What Are You Doing? 15 | 16 | When setting out with the task of developing a tool you should, as a first step, stop and think. Think about 17 | what problem your tool is trying to solve. This could be a number of things 18 | 19 | - Creating data 20 | - collating data 21 | - Interacting with a system or systems 22 | 23 | The sky is the limit here, but your first thing is to determine what it 24 | is that you are trying to accomplish. 25 | 26 | ## What Should You Call It? 27 | 28 | Your second step should be to consider your tool's name. Whether this is a single function, or a series of functions 29 | that form a new module, you should consider the following: 30 | 31 | - Use [approved verbs](https://learn.microsoft.com/powershell/scripting/developer/cmdlet/approved-verbs-for-windows-powershell-commands) for Functions. You can run `Get-Verb` in your console to quickly get a list! _Tip_: Use `Get-Verb | Sort-Object` to make this easier to parse! 32 | - Use a coherent noun. Be as specific as possible. Using a great combination of verb/noun syntax provides clarity 33 | to what your tool does. 34 | 35 | ## Designing Parameters 36 | 37 | This step _could_ take some time, and a little trial and error. You want your tool to be flexible, but you don't want your parameter 38 | names to be so difficult such that they are hard to use/remember. Succinct is better here. If you need to add some flexibility to your 39 | tool, considering using [ParameterSets](https://learn.microsoft.com/powershell/module/microsoft.powershell.core/about/about_parameter_sets). These will give your end users a few different ways to use your tool, if that is or becomes necessary in the future. 40 | 41 | ### Applying Guardrails 42 | 43 | Guardrails, in this context, refers to the application of restrictions upon your parameters. These prevent your end users from passing incorrect input 44 | to the tool you've provided them. Given that PowerShell is built on .NET, there is a _ton_ of flexibility and strength in the guardrails you can employ. 45 | 46 | I'll touch on just a few of my favorites, but this is by far not an exhaustive list. 47 | 48 | #### 1. ValidateSet 49 | 50 | Let's look at an example first: 51 | 52 | ```powershell 53 | [CmdletBinding()] 54 | Param( 55 | [Parameter()] 56 | [ValidateSet('Cat','Dog','Fish','Bird')] 57 | [String] 58 | $Animal 59 | ) 60 | ``` 61 | 62 | If you notice above, we've defined a non-mandatory parameter that is of type `[String]`. If you notice above, we've defined a non-mandatory parameter that is of type `[String]`. This is a guardrail because any other type causes an error to be thrown. 63 | We have added further restrictions (guardrails) on this parameter by employing a `[ValidateSet()]` attribute, which limits the valid input to _only_ those items that are 64 | a member of the set. Provide `Horse` to the animal parameter and, even though it is a string, it produces an error because it's not a member of the approved set of inputs. 65 | 66 | #### 2. ValidateRange 67 | 68 | We'll start with another example: 69 | 70 | ```powershell 71 | [CmdletBinding()] 72 | Param( 73 | [Parameter()] 74 | [ValidateRange(2005,2023)] 75 | [Int] 76 | $Year 77 | ) 78 | ``` 79 | 80 | In this example we have defined a `Year` parameter that is an `[Int]`, meaning only numbers are valid input. We've applied guardrails via `[ValidateRange()]`, which limits the input to between 2005 and 2023. Any number outside of that range produces an error. 81 | 82 | #### 3. ValidateScript 83 | 84 | The `[ValidateScript()]` attribute is extremely powerful. It allows you to run arbitrary PowerShell code in a script block to check the input of a given parameter. 85 | Let's check out a _very_ simple example: 86 | 87 | ```powershell 88 | [CmdletBinding()] 89 | Param( 90 | [Parameter()] 91 | [ValidateScript({ Test-Path $_ })] 92 | [String] 93 | $InputFile 94 | ) 95 | ``` 96 | 97 | By using `Test-Path $_` in the Scriptblock of our `[ValidateScript()]` attribute we are instructing 98 | PowerShell to confirm that the input we have provided to the parameter actually exists (_Notice the 99 | addition of `{}` here_). This helps by putting guardrails around human error in the form of typos. 100 | 101 | ## Wrapping It Up 102 | 103 | As previously stated, adding guardrails to your tools using these methods (and countless others not mentioned) _demonstrably_ increases the usability and adoption of your tools. 104 | 105 | So take a step back, think about your tool's design _first_, and then start writing the code. I 106 | think you'll find that it is a much more enjoyable experience, from creation to adoption. 107 | -------------------------------------------------------------------------------- /Posts/2023/05/Media/Porting-GeneratePassword-From-Csharp/File-OpenFromGAC.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PowerShell/Community-Blog/7837d320a6bd85ca7d4469dcfaa115112333fac0/Posts/2023/05/Media/Porting-GeneratePassword-From-Csharp/File-OpenFromGAC.png -------------------------------------------------------------------------------- /Posts/2023/05/Media/Porting-GeneratePassword-From-Csharp/GeneratePasswordMethod.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PowerShell/Community-Blog/7837d320a6bd85ca7d4469dcfaa115112333fac0/Posts/2023/05/Media/Porting-GeneratePassword-From-Csharp/GeneratePasswordMethod.png -------------------------------------------------------------------------------- /Posts/2023/05/Media/Porting-GeneratePassword-From-Csharp/MembershipClass.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PowerShell/Community-Blog/7837d320a6bd85ca7d4469dcfaa115112333fac0/Posts/2023/05/Media/Porting-GeneratePassword-From-Csharp/MembershipClass.png -------------------------------------------------------------------------------- /Posts/2023/05/Media/Porting-GeneratePassword-From-Csharp/OpenFromGACMenu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PowerShell/Community-Blog/7837d320a6bd85ca7d4469dcfaa115112333fac0/Posts/2023/05/Media/Porting-GeneratePassword-From-Csharp/OpenFromGACMenu.png -------------------------------------------------------------------------------- /Posts/2023/05/Media/Porting-GeneratePassword-From-Csharp/Result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PowerShell/Community-Blog/7837d320a6bd85ca7d4469dcfaa115112333fac0/Posts/2023/05/Media/Porting-GeneratePassword-From-Csharp/Result.png -------------------------------------------------------------------------------- /Posts/2023/07/Changing-Console-Title.md: -------------------------------------------------------------------------------- 1 | --- 2 | post_title: 'Changing your console window title' 3 | username: FranciscoNabas 4 | categories: PowerShell 5 | post_slug: changing-console-title 6 | tags: PowerShell, Automation, console, terminal 7 | summary: This post shows how to change the title of your console terminal window. 8 | --- 9 | 10 | As our skill as a PowerShell developer grows, and the complexity of our scripts increase, we start 11 | incorporating new elements to improve the user experience. That might include changing fonts, the 12 | background color, or the console window title. This task was already discussed in a blog post from 13 | 2004, [Can I Change the Command Window Title When Running a Script?][01]. However, the post uses VB 14 | script, and changes the title if you are willing to open a new console. Today we learn how to do it 15 | with PowerShell, using the same window. 16 | 17 | ## Methods 18 | 19 | We will explore two ways of changing the console window title. 20 | 21 | - The `$Host` automatic variable. 22 | - Console virtual terminal sequences. 23 | 24 | ## The $Host automatic variable 25 | 26 | This variable contains an object that represents the current host application for PowerShell. This 27 | object contains a property called `$Host.UI.RawUI` that allows us to change various aspects of the 28 | current PowerShell host, including the window title. Here is how we do it. 29 | 30 | ```powershell 31 | $Host.UI.RawUI.WindowTitle = 'MyCoolWindowTitle!' 32 | ``` 33 | 34 | And with just a property value change our window title changed. 35 | 36 | ![RawUI.WindowTitle](./Media/WindowTitle.png) 37 | 38 | For as simple and straight forward the previous method is, there is something to keep in mind. The 39 | `$Host` automatic variable is host dependent. 40 | 41 | ## Virtual terminal sequences 42 | 43 | Console virtual terminal sequences are control character sequences that can control various aspects 44 | of the console when written to the output stream. The terminal sequences are intercepted by the 45 | console host when written into the output stream. To see all sequences, and more in-depth examples 46 | go to the [Microsoft documentation page][02]. Virtual terminal sequences are preferred because they 47 | follow a well-defined standard, and are fully documented. The window title is limited to 255 48 | characters. 49 | 50 | To change the window title the sequence is `ESC]0;<string><ST>` or `ESC]2;<string><ST>`, where 51 | 52 | - `ESC` is character 0x1B. 53 | - `<ST>` is the string terminator, which in this case is the "Bell" character 0x7. 54 | 55 | The bell character can also be used with the escape sequence `\a`. Here is how we change a console 56 | window title with virtual terminal sequences. 57 | 58 | ```powershell 59 | $title = 'Title with terminal sequences!' 60 | 61 | Write-Host "$([char]0x1B)]0;$title$([char]0x7)" 62 | 63 | # Using the escape sequence. 64 | Write-Host "$([char]0x1B)]0;$title`a" 65 | ``` 66 | 67 | ## Conclusion 68 | 69 | PowerShell is a versatile tool that often provides multiple ways of achieving the same goal. I hope 70 | you had as much fun reading as I had writing. See you in the next one. 71 | 72 | Happy scripting! 73 | 74 | Useful links: 75 | 76 | - [PowerShell automatic variable][03] 77 | - [xterm terminal emulator][05] 78 | - [Escape sequences][06] 79 | 80 | Test our PowerShell module: 81 | 82 | - [WindowsUtils][07] 83 | 84 | <!-- link references --> 85 | [01]: https://devblogs.microsoft.com/scripting/can-i-change-the-command-window-title-when-running-a-script/ 86 | [02]: https://learn.microsoft.com/windows/console/console-virtual-terminal-sequences 87 | [03]: https://learn.microsoft.com/powershell/module/microsoft.powershell.core/about/about_automatic_variables#home 88 | [05]: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html 89 | [06]: https://learn.microsoft.com/cpp/c-language/escape-sequences 90 | [07]: https://github.com/FranciscoNabas/WindowsUtils 91 | -------------------------------------------------------------------------------- /Posts/2023/07/Media/WindowTitle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PowerShell/Community-Blog/7837d320a6bd85ca7d4469dcfaa115112333fac0/Posts/2023/07/Media/WindowTitle.png -------------------------------------------------------------------------------- /Posts/2023/11/automate-text-summarization-with-openai-powershell.md: -------------------------------------------------------------------------------- 1 | --- 2 | post_title: 'Automate Text Summarization with OpenAI and PowerShell' 3 | username: thepiyush13 4 | categories: PowerShell, OpenAI, Scripting 5 | post_slug: automate-text-summarization-with-openai-powershell 6 | tags: PowerShell, OpenAI, GPT-3.5, API, Summarization 7 | summary: This easy-to-follow guide shows you how to use PowerShell to summarize text using OpenAI's GPT-3.5 API. 8 | --- 9 | 10 | Automating tasks is the core of PowerShell scripting. Adding artificial intelligence into the mix 11 | takes automation to a whole new level. Today, we'll simplify the process of connecting to OpenAI's 12 | powerful text summarization API from PowerShell. Let's turn complex AI interaction into a 13 | straightforward script. 14 | 15 | To follow this guide, you'll need an OpenAI API key. If you don't already have one, you'll need to 16 | create an [OpenAI account][01] or [sign in][02] to an existing one. Next, navigate to the 17 | [API key page][03] and create a new secret key to use. 18 | 19 | ## Step-by-Step Function Creation 20 | 21 | ### Step 1: Define the Function and Parameters 22 | 23 | We'll start by setting up our function with parameters such as the API key and text to summarize: 24 | 25 | ```powershell 26 | function Invoke-OpenAISummarize { 27 | param( 28 | [string]$apiKey, 29 | [string]$textToSummarize, 30 | [int]$maxTokens = 60, 31 | [string]$engine = 'davinci' 32 | ) 33 | # You can add or remove parameters as per your requirements 34 | } 35 | ``` 36 | 37 | ### Step 2: Set Up API Connection Details 38 | 39 | Next, we'll prepare our connection to OpenAI's API by specifying the URL and headers: 40 | 41 | ```powershell 42 | $uri = "https://api.openai.com/v1/engines/$engine/completions" 43 | $headers = @{ 44 | 'Authorization' = "Bearer $apiKey" 45 | 'Content-Type' = 'application/json' 46 | } 47 | ``` 48 | 49 | ### Step 3: Construct the Body of the Request 50 | 51 | We need to tell the API what we want it to do: summarize text. We do this in the request body: 52 | 53 | ```powershell 54 | $body = @{ 55 | prompt = "Summarize the following text: `"$textToSummarize`"" 56 | max_tokens = $maxTokens 57 | n = 1 58 | } | ConvertTo-Json 59 | ``` 60 | 61 | ### Step 4: Make the API Request and Return the Summary 62 | 63 | The final part of the function sends the request and then gets the summary back from the API: 64 | 65 | ```powershell 66 | $parameters = @{ 67 | Method = 'POST' 68 | URI = $uri 69 | Headers = $headers 70 | Body = $body 71 | ErrorAction = 'Stop' 72 | } 73 | 74 | try { 75 | $response = Invoke-RestMethod @parameters 76 | return $response.choices[0].text.Trim() 77 | } catch { 78 | Write-Error "Failed to invoke OpenAI API: $_" 79 | return $null 80 | } 81 | } 82 | ``` 83 | 84 | ## Running the Function 85 | 86 | Now, to use the function, you just need two pieces of information: your OpenAI API key and the text 87 | to summarize. 88 | 89 | ```powershell 90 | $summary = Invoke-OpenAISummarize -apiKey 'Your_Key' -textToSummarize 'Your text...' 91 | Write-Output "Summary: $summary" 92 | ``` 93 | 94 | Replace `'Your__Key'` with your actual key and `'Your text...'` with what you want to summarize. 95 | 96 | Here's a how I am running this function in my local PowerShell prompt, I copied 97 | the text from Wikipedia: 98 | 99 | ```powershell 100 | $summary = Invoke-OpenAISummarize -apiKey '*********' -textToSummarize @' 101 | PowerShell is a task automation and configuration management program from 102 | Microsoft, consisting of a command-line shell and the associated scripting 103 | language. Initially a Windows component only, known as Windows PowerShell, 104 | it was made open-source and cross-platform on August 18, 2016, with the 105 | introduction of PowerShell Core.[5] The former is built on the .NET Framework, 106 | the latter on .NET (previously .NET Core). 107 | '@ 108 | ``` 109 | 110 | and I get the following result: 111 | 112 | ``` 113 | PowerShell, initially Windows-only, is a Microsoft automation tool that became 114 | cross-platform as open-source PowerShell Core, transitioning from .NET Framework 115 | to .NET. 116 | ``` 117 | 118 | ## Conclusion 119 | 120 | Combining AI with PowerShell scripting is like giving superpowers to your computer. By breaking 121 | down each step and keeping it simple, you can see how easy it is to automate text summarization 122 | using OpenAI's GPT-3.5 API. Now, try it out and see how you can make this script work for you! 123 | 124 | Remember, the beauty of scripts is in their flexibility, so feel free to tweak and expand the 125 | function to fit your needs. 126 | 127 | Happy scripting and enjoy the power of AI at your fingertips! 128 | 129 | ## References 130 | 131 | - [OpenAI API reference documentation][04] 132 | 133 | [01]: https://platform.openai.com/signup 134 | [02]: https://platform.openai.com/login 135 | [03]: https://platform.openai.com/account/api-keys 136 | [04]: https://platform.openai.com/docs/api-reference 137 | -------------------------------------------------------------------------------- /Posts/2023/11/powershell-twilio-contact-tracing-communication.md: -------------------------------------------------------------------------------- 1 | --- 2 | post_title: 'Using PowerShell and Twilio API for Efficient Communication in Contact Tracing' 3 | username: will2win4u 4 | categories: PowerShell, Twilio, Communication Technology 5 | post_slug: powershell-twilio-contact-tracing-communication 6 | tags: PowerShell, Twilio, API, Communication Technology, Contact Tracing 7 | summary: Learn to integrate PowerShell with Twilio API and streamline communication for COVID-19 contact tracing initiatives. 8 | --- 9 | 10 | The COVID-19 pandemic has underscored the importance of rapid and reliable communication technology. 11 | One vital application is in contact tracing efforts, where prompt notifications can make a 12 | significant difference. This guide focuses on utilizing PowerShell in conjunction with the Twilio 13 | API to establish an automated SMS notification system, an essential communication tool for contact 14 | tracing. 15 | 16 | ## Integrating Twilio with PowerShell 17 | 18 | ### Registering and Preparing Twilio Credentials 19 | 20 | Before diving into scripting, you need to create a Twilio account. Once registered, obtain your 21 | Account SID and Auth Token. These credentials are the keys to accessing Twilio's SMS services. Then, 22 | choose a Twilio phone number, which will be the source of your outgoing messages. 23 | 24 | ### PowerShell Scripting to Send SMS via Twilio 25 | 26 | With your Twilio environment prepared, the next step is to configure PowerShell to interact with 27 | Twilio's API. Start by storing your Twilio credentials as environmental variables or securely within 28 | your script, ensuring they are not exposed or hard-coded. 29 | 30 | ```powershell 31 | $twilioAccountSid = 'Your_Twilio_SID' 32 | $twilioAuthToken = 'Your_Twilio_Auth_Token' 33 | $twilioPhoneNumber = 'Your_Twilio_Number' 34 | ``` 35 | 36 | After the setup and with the appropriate Twilio module installed, crafting a PowerShell script to 37 | dispatch an SMS using Twilio's API is straightforward: 38 | 39 | ```powershell 40 | Import-Module Twilio 41 | 42 | $toPhoneNumber = 'Recipient_Phone_Number' 43 | $credential = [pscredential]:new($twilioAccountSid, 44 | (ConvertTo-SecureString $twilioAuthToken -AsPlainText -Force)) 45 | 46 | # Twilio API URL for sending SMS messages 47 | $uri = "https://api.twilio.com/2010-04-01/Accounts/$twilioAccountSid/Messages.json" 48 | 49 | # Preparing the payload for the POST request 50 | $requestParams = @{ 51 | From = $twilioPhoneNumber 52 | To = $toPhoneNumber 53 | Body = 'Your body text here.' 54 | } 55 | 56 | $invokeRestMethodSplat = @{ 57 | Uri = $uri 58 | Method = 'Post' 59 | Credential = $credential 60 | Body = $requestParams 61 | } 62 | 63 | # Using the Invoke-RestMethod command for API interaction 64 | $response = Invoke-RestMethod @invokeRestMethodSplat 65 | ``` 66 | 67 | Execute the script, and if all goes as planned, you should see a confirmation of the SMS being sent. 68 | 69 | ### Preparing Data for Automated Notifications 70 | 71 | Before we can automate the sending of notifications, we need to have our contact data organized and 72 | accessible. This is typically done by creating a CSV file, which PowerShell can easily parse and 73 | utilize within our script. 74 | 75 | #### Creating a CSV File 76 | 77 | A CSV (Comma-Separated Values) file is a plain text file that contains a list of data. For contact 78 | tracing notifications, we can create a CSV file that holds the information of individuals who need 79 | to receive SMS alerts. Here is an example of what the content of this CSV file might look like: 80 | 81 | ```csv 82 | Name,Phone 83 | John Doe,+1234567890 84 | Jane Smith,+1098765432 85 | Alex Johnson,+1123456789 86 | ``` 87 | 88 | In this simple table, each column is separated by a comma. The first row is the header, which 89 | describes the content of each column. Subsequent rows contain the data for each person, with their 90 | name and phone number. 91 | 92 | ### Automating the Process for Contact Tracing 93 | 94 | Once manual sending is confirmed and the CSV file is ready, you can move towards automating the 95 | process for contact tracing: 96 | 97 | ```powershell 98 | Import-Module Twilio 99 | 100 | $contactList = Import-Csv -Path 'contact_list.csv' 101 | 102 | # Create Twilio API credentials 103 | $credential = [pscredential]:new($twilioAccountSid, 104 | (ConvertTo-SecureString $twilioAuthToken -AsPlainText -Force)) 105 | 106 | # Twilio API URL for sending SMS messages 107 | $uri = "https://api.twilio.com/2010-04-01/Accounts/$twilioAccountSid/Messages.json" 108 | 109 | foreach ($contact in $contactList) { 110 | $requestParams = @{ 111 | From = $twilioPhoneNumber 112 | To = $contact.Phone 113 | Body = "Please be informed of a potential COVID-19 exposure. Follow public health guidelines." 114 | } 115 | 116 | $invokeRestMethodSplat = @{ 117 | Uri = $uri 118 | Method = 'Post' 119 | Credential = $credential 120 | Body = $requestParams 121 | } 122 | $response = Invoke-RestMethod @invokeRestMethodSplat 123 | 124 | # Log or take action based on $response as needed 125 | } 126 | ``` 127 | 128 | By looping through a list of contacts and sending a personalized SMS to each, you're leveraging 129 | automation for mass communication—a critical piece in the contact tracing puzzle. 130 | 131 | ## Conclusion 132 | 133 | In this post, we've reviewed how to establish a bridge between PowerShell and Twilio's messaging API 134 | to execute automated SMS notifications. Such integrations are at the heart of communication 135 | technology advancements, facilitating critical public health operations like contact tracing. 136 | 137 | ## References 138 | - [https://www.twilio.com/docs/api](https://www.twilio.com/docs/api) 139 | - [https://www.twilio.com/try-twilio](https://www.twilio.com/try-twilio) 140 | -------------------------------------------------------------------------------- /Posts/2024/02/Media/creating-a-scalable-customised-running-environment/ModuleSetup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PowerShell/Community-Blog/7837d320a6bd85ca7d4469dcfaa115112333fac0/Posts/2024/02/Media/creating-a-scalable-customised-running-environment/ModuleSetup.png -------------------------------------------------------------------------------- /Posts/2024/02/creating-a-scalable-customised-running-environment.md: -------------------------------------------------------------------------------- 1 | --- 2 | post_title: 'Creating a scalable, customised running environment' 3 | username: rod-meaney 4 | categories: PowerShell 5 | post_slug: creating-a-scalable-customised-running-environment 6 | tags: PowerShell, Automation, Toolmaking, User Experience 7 | summary: This post shows how to create an easy to support environment with all your own cmdlets. 8 | --- 9 | 10 | Often people come to PowerShell as a developer looking for a simpler life, or as a support person 11 | looking to make their life easier. Either way, we start exploring ways to encapsulate repeatable 12 | functionality, and through PowerShell that is cmdlets. 13 | 14 | How to create these is defined well in [Designing PowerShell For End Users][01]. And Microsoft 15 | obviously have pretty good documention, including [How to Write a PowerShell Script Module][02]. I 16 | also have a few basic rules I remember wehen creating cmdlets to go along with the above posts: 17 | 18 | - Always use cmdlet binding. 19 | - Call the file name the same as the cmdlet, without the dash. 20 | 21 | But how do you organise them and ensure that they always load. This post outlines an approach that 22 | has worked well for me across a few different jobs, with a few iterations to get to this point. 23 | 24 | ## Methods 25 | 26 | There are 2 parts to making an effective running environment 27 | 28 | - Ensuring all your cmdlets for a specific module will load. 29 | - Ensuring all your modules will load. 30 | 31 | ### Example setup 32 | 33 | ![folder-structure][03] 34 | 35 | We are aiming high here. Over time your functionality will grow and this shows a structure that 36 | allows for growth. There are 3 modules (effectively folders): `Forms`, `BusinessUtilities` and 37 | `GeneralUtilities`. They are broken up into 2 main groupings, `my-support` and `my-utilities`. 38 | [ps-community-blog][04] is the GitHub repository where you can find this example. 39 | 40 | Inside the `GenreralUtilities` folder you can see the all-important `.psm1`, with the same name as 41 | the folder and a couple of cmdlets I have needed over the years. The `.psm1` file is a requirement 42 | to create a module. 43 | 44 | ## Ensuring all your cmdlets for a specific module will load 45 | 46 | Most descriptions of creating modules will explain that you need to either add the cmdlet into the 47 | `.psm1`, or load the cmdlet files in the `.psm1` file. Instead, put the below in ALL your `.psm1` 48 | module files: 49 | 50 | ```powershell 51 | Get-ChildItem -Path "$PSScriptRoot\*.ps1" | ForEach-Object { 52 | . $PSScriptRoot\$($_.Name) 53 | } 54 | ``` 55 | 56 | What does this do and why does it work? 57 | 58 | - At a high level, it iterates over the current folder, and runs every `.ps1` file as PowerShell. 59 | - `$PSScriptRoot` is the key here, and tells running session, what the location of the current 60 | code is. 61 | 62 | This means you can create cmdlets under this structure, and they will automatically load when you 63 | start up a new PowerShell session. 64 | 65 | ## Ensuring all your modules will load 66 | 67 | So, the modules are sorted. How do we make sure the modules themselves load? It's all about the 68 | `Profile.ps1`. You will either find it or need to create it in: 69 | 70 | - PowerShell 5 and lower - `$HOME\Documents\WindowsPowerShell\Profile.ps1`. 71 | - PowerShell 7 - `$HOME\Documents\PowerShell\Profile.ps1`. 72 | - For detailed information, see [About Profiles][05]. 73 | 74 | So this file runs at the start of every session that is opened on your machine. I have included 75 | both 5 and 7, as in a lot of corporate environments, 5 is all that is available, and often people 76 | don't have access to modify their environment. With some simple code we can ensure our modules will 77 | open. Add this into your `Profile.ps1`: 78 | 79 | ```powershell 80 | Write-Host "Loading Modules for Day-to-Day use" 81 | $ErrorActionPreference = "Stop" # A safeguard for any errors 82 | 83 | $MyModuleDef = @{ 84 | Utilities = @{ 85 | Path = "C:\work\git-personal\ps-community-blog\my-utilities" 86 | Exclude = @(".git") 87 | } 88 | Support = @{ 89 | Path = "C:\work\git-personal\ps-community-blog\my-support" 90 | Exclude = @(".git") 91 | } 92 | } 93 | 94 | foreach ($key in $MyModuleDef.Keys) { 95 | $module = $MyModuleDef[$key] 96 | $exclude = $module.Exclude 97 | 98 | $env:PSModulePath = @( 99 | $env:PSModulePath 100 | $module.Path 101 | ) -join [System.IO.Path]::PathSeparator 102 | 103 | Get-ChildItem -Path $module.Path -Directory -Exclude $exclude | 104 | ForEach-Object { 105 | Write-Host "Loading Module $($_.Name) in $Key" 106 | Import-Module $_.Name 107 | } 108 | } 109 | ``` 110 | 111 | What does this do and why does it work? 112 | 113 | - At a high level, it defines your module groupings, then loads your modules into the PowerShell 114 | session. 115 | - `$MyModuleDef` contains the reference to your module groupings, to make sure all the sub folders 116 | are loaded as modules. 117 | - `Exclude` is very important. You may load the code directly of your code base, so ignoring those 118 | as modules is important. I have also put DLL's in folders in module groupings, and ignoring these 119 | is important as well. 120 | 121 | Now, every time you open any PowerShell session on your machine, all your local cmdlets will be 122 | there, ready to use with all the wonderful functionality you have created. 123 | 124 | ## Conclusion 125 | 126 | Having your own PowerShell cmdlets at your fingertips with minimal overhead or thinking makes your 127 | PowerShell experinece so very much more rewarding. It also makes it easier to do as I like to do 128 | and start the day with my favourite mantra: 129 | 130 | > Lets break some stuff! 131 | 132 | <!-- link references --> 133 | [01]: https://devblogs.microsoft.com/powershell-community/designing-powershell-for-end-users/ 134 | [02]: https://learn.microsoft.com/en-us/powershell/scripting/developer/module/how-to-write-a-powershell-script-module 135 | [03]: ./Media/creating-a-scalable-customised-running-environment/ModuleSetup.png 136 | [04]: https://github.com/rod-meaney/ps-community-blog 137 | [05]: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_profiles 138 | -------------------------------------------------------------------------------- /Posts/2024/03/Media/simple-form-development-using-powershell/JsonAndCmdlet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PowerShell/Community-Blog/7837d320a6bd85ca7d4469dcfaa115112333fac0/Posts/2024/03/Media/simple-form-development-using-powershell/JsonAndCmdlet.png -------------------------------------------------------------------------------- /Posts/2024/03/Media/simple-form-development-using-powershell/LaunchingASimpleForm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PowerShell/Community-Blog/7837d320a6bd85ca7d4469dcfaa115112333fac0/Posts/2024/03/Media/simple-form-development-using-powershell/LaunchingASimpleForm.png -------------------------------------------------------------------------------- /Posts/2024/04/Media/encrypting-secrets-locally/KeyValueStore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PowerShell/Community-Blog/7837d320a6bd85ca7d4469dcfaa115112333fac0/Posts/2024/04/Media/encrypting-secrets-locally/KeyValueStore.png -------------------------------------------------------------------------------- /Posts/2024/04/encrypting-secrets-locally.md: -------------------------------------------------------------------------------- 1 | --- 2 | post_title: 'Encrypting secrets locally' 3 | username: rod-meaney 4 | categories: PowerShell 5 | post_slug: encrypting-secrets-locally 6 | tags: Automation, Toolmaking, Security 7 | summary: Keeping security folks happy (or less upset which is the best we can hope for) 8 | --- 9 | 10 | If you are involved in support or development, often you need to use secrets, passwords, or 11 | subscription keys in PowerShell scripts. These need to be kept secure and separate from your scripts 12 | but you also need access to them ALL THE TIME. 13 | 14 | So instead of hand entering them every time they should be stored in a key store of some sort that 15 | you can access programmatically. Often off the shelf keystores are not available in your environment 16 | or are clumsy to access with PowerShell. A simple way to have easy access to these secrets with 17 | PowerShell would be helpful. 18 | 19 | You could simply have them in plain text, on your machine only, making it relatively secure. 20 | However, there are many risks with this approach, so adding some additional security is an excellent 21 | idea. 22 | 23 | The .NET classes sitting behind PowerShell provide some simple ways to do this. This blog will go 24 | through 25 | 26 | - Basic encryption / decryption 27 | - Using it day-to-day 28 | - Your own form-based key store 29 | 30 | ## Basic encryption / decryption 31 | 32 | The [protect][07] and [unprotect][08] methods available as part of the cryptography classes are 33 | easy to use. However they use Byte arrays that we can simplify by wrapping their use in a String. 34 | 35 | The following examples can be found at the [MachineAndUserEncryption.ps1][06] module in my 36 | [ps-community-blog][04] repository on GitHub. 37 | 38 | ### Encryption 39 | 40 | ```powershell 41 | Function Protect-WithUserKey { 42 | param( 43 | [Parameter(Mandatory=$true)] 44 | [string]$secret 45 | ) 46 | Add-Type -AssemblyName System.Security 47 | $bytes = [System.Text.Encoding]::Unicode.GetBytes($secret) 48 | $SecureStr = [Security.Cryptography.ProtectedData]::Protect( 49 | $bytes, # contains data to encrypt 50 | $null, # optional data to increase entropy 51 | [Security.Cryptography.DataProtectionScope]::CurrentUser # scope of the encryption 52 | ) 53 | $SecureStrBase64 = [System.Convert]::ToBase64String($SecureStr) 54 | return $SecureStrBase64 55 | } 56 | ``` 57 | 58 | Just going through the lines we can see 59 | 60 | 1. PowerShell needs to know about the .NET classes (I have tested under version 5 & 7 of PowerShell) 61 | 1. We need to convert our string into a Byte array 62 | 1. Use the .NET class to encrypt 63 | 1. Convert the encrypted Byte array to a string for easy storage and retrieval 64 | 1. Return that string 65 | 66 | ### Decryption 67 | 68 | ```powershell 69 | Function Unprotect-WithUserKey { 70 | param ( 71 | [Parameter(Mandatory=$true)] 72 | [string]$enc_secret 73 | ) 74 | Add-Type -AssemblyName System.Security 75 | $SecureStr = [System.Convert]::FromBase64String($enc_secret) 76 | $bytes = [Security.Cryptography.ProtectedData]::Unprotect( 77 | $SecureStr, # bytes to decrypt 78 | $null, # optional entropy data 79 | [Security.Cryptography.DataProtectionScope]::CurrentUser) # scope of the decryption 80 | $secret = [System.Text.Encoding]::Unicode.GetString($bytes) 81 | return $secret 82 | } 83 | ``` 84 | 85 | Steps are identical for the decryption, using slightly different methods 86 | 87 | 1. PowerShell needs to know about the .NET classes 88 | 1. We need to convert our string into a Byte array 89 | 1. Use the .NET class to decrypt 90 | 1. Convert the encrypted Byte array to a string 91 | 1. Return that string 92 | 93 | ## Using it day-to-day 94 | 95 | This is really useful if you are doing repetitive tasks that need these values. Often in a support 96 | role, investigations using API's can speed up the process of analysis, and also provide you with a 97 | quick way to do fixes that don't require heavy use of a GUI based environment. 98 | 99 | Assigning a key to a secret value, and storing that in a hash table format is the simplest way to 100 | have access to these values AND keep them stored locally with a degree of security. Your code can 101 | then dynamically look up these values, and if other support people store the same key locally the 102 | same way (often with different values, think of an API password and or username pair) then your 103 | script can work for everyone. 104 | 105 | Again, `MachineAndUserEncryption.ps1` in my repository on my GitHub has functions for persisting and 106 | using this information. For compatibility with version 5 & 7 you also need the function 107 | [ConvertToHashtableV5][05]. 108 | 109 | I would also recommend using `Protect-WithMachineAndUserKey` and `Unprotect-WithMachineAndUserKey` 110 | when implementing locally, they add another layer of protection. 111 | 112 | ## Your own form-based key store 113 | 114 | If you have followed my other 2 blogs about a [scalable environment][02] and 115 | [simple form development][03] then using the resources from these we can easily create our own form 116 | to manage our secrets. In fact, if you have downloaded and installed the modules for either of those 117 | blogs (they are the same, and this blog references the same as well), you have it ready to go. 118 | 119 | Once you have your environment set up, simply run the cmdlet: 120 | 121 | ```powershell 122 | New-EncryptKeyForm 123 | ``` 124 | 125 | and if all is set up correctly, you should see 126 | 127 | ![key-value-secret-store][01] 128 | 129 | ## Conclusion 130 | 131 | Balancing the pragmatic ease of use and security concerns around secrets you may need to use all day 132 | every day can be a fine balancing act. Using some simple methods, we can strike that balance and 133 | hopefully be securely productive. 134 | 135 | > Lets secure some stuff! 136 | 137 | <!-- link references --> 138 | [01]: ./Media/encrypting-secrets-locally/KeyValueStore.png 139 | [02]: https://devblogs.microsoft.com/powershell-community/creating-a-scalable-customised-running-environment/ 140 | [03]: https://devblogs.microsoft.com/powershell-community/simple-form-development-using-powershell/ 141 | [04]: https://github.com/rod-meaney/ps-community-blog 142 | [05]: https://github.com/rod-meaney/ps-community-blog/blob/main/my-utilities/GeneralUtilities/ConvertToHashtableV5.ps1 143 | [06]: https://github.com/rod-meaney/ps-community-blog/blob/main/my-utilities/GeneralUtilities/MachineAndUserEncryption.ps1 144 | [07]: https://learn.microsoft.com/dotnet/api/system.security.cryptography.protecteddata.protect 145 | [08]: https://learn.microsoft.com/dotnet/api/system.security.cryptography.protecteddata.unprotect -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PowerShell Community Blog 2 | 3 | This repository is for submissions for posts to the [PowerShell Community Blog][03]. We welcome 4 | submissions to the blog from anyone in the community. 5 | 6 | ## The Purpose of This Blog 7 | 8 | The intended purpose of the PowerShell Community Blog is to provide a platform for the PowerShell 9 | Community to show off the great things you can do with PowerShell. This blog welcomes submissions to 10 | the blog both from internal Microsoft teams and external people. Blog posts can cover the open 11 | source PowerShell 7 or Windows PowerShell. Your post should clearly outline the problem the being 12 | solved. Your post can cover an advanced topic, but most posts are likely to be at the 200-300 level. 13 | For advanced topics, consider splitting your subject into a multipart series of posts. 14 | 15 | Posts to the blog can discuss products and technologies that aren't part of the core PowerShell 16 | product or even made by Microsoft, as long as the post's content is relevant to PowerShell users and 17 | is not marketing those products. 18 | 19 | The published language for the PowerShell community is English, and mainly American English, 20 | although posts other variations of English are acceptable. The article review process focuses on the 21 | language and structure of each article, as well as the specific details. Even if English isn't your 22 | first language, the review process can help to iron out any problems. 23 | 24 | ## How to Interact 25 | 26 | There are several ways you can interact, depending on your needs and levels of enthusiasm. 27 | 28 | 1. First is to read and enjoy the blog. Over time, we hope and expect the major search engines to 29 | index these posts, making it easy for IT Pros to find and use the information contained. 30 | 31 | 1. You can comment on any of blog posts directly on the blog. This blog uses Word Press, so in order 32 | to add comments, you need to create and then login to a Word Press account. Once you logon 33 | successfully to the blog, Word Press allows you to add comments to the posts here. 34 | 35 | To create a Word Press account, see our [Community Blog docs][06] for detailed instructions. We 36 | welcome comments and prefer them in English. You can use an online translator like 37 | [Bing Translator][07] if English isn't your first language. 38 | 39 | 1. You may want to contribute your own blog post. You can create new posts, file issues on any 40 | article (or proposed article), or help review content submissions. If you find an error, feel 41 | free to [file an issue on GitHub][05]. You can also file an issue to suggest a specific topic you 42 | feel might make a good blog post. 43 | 44 | You need a GitHub account to be able to submit anything to the blog's GitHub repository. You can 45 | sign up for GitHub at [GitHub's new account sign up page][04]. And, you need to be able to use GitHub 46 | and, most likely, git on your workstation. 47 | 48 | > [!NOTE] 49 | > Acceptance of any blog post is done at the sole discretion of the Blog administrators. Once you 50 | > submit a PR, the build automation adds a comment to the PR asking you to sign the CLA. The comment 51 | > contains a link to take you to the CLA signing page. Before we can accept any blog post 52 | > submission, you must sign the Contributor License Agreement (CLA). This is a one-time event. 53 | 54 | ## Code of Conduct 55 | 56 | Please see our [Code of Conduct][01] before participating in this project. 57 | 58 | ## Security Policy 59 | 60 | For any security issues, please see our [Security Policy][02]. 61 | 62 | <!-- link references --> 63 | [01]: .github/CODE_OF_CONDUCT.md 64 | [02]: .github/SECURITY.md 65 | [03]: https://devblogs.microsoft.com/powershell-community 66 | [04]: https://github.com/join?source=login 67 | [05]: https://github.com/PowerShell/Community-Blog/issues 68 | [06]: https://github.com/PowerShell/Community-Blog/tree/main/Docs 69 | [07]: https://www.bing.com/translator 70 | --------------------------------------------------------------------------------