{"id":71942,"date":"2015-07-25T00:01:00","date_gmt":"2015-07-25T00:01:00","guid":{"rendered":"https:\/\/blogs.technet.microsoft.com\/heyscriptingguy\/2015\/07\/25\/weekend-scripter-replace-one-powershell-function-with-another\/"},"modified":"2022-06-22T12:34:00","modified_gmt":"2022-06-22T19:34:00","slug":"weekend-scripter-replace-one-powershell-function-with-another","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/scripting\/weekend-scripter-replace-one-powershell-function-with-another\/","title":{"rendered":"Weekend Scripter: Replace One PowerShell Function with Another"},"content":{"rendered":"<p><b style=\"font-size:12px\">Summary<\/b><span style=\"font-size:12px\">: Ed Wilson, Microsoft Scripting Guy, talks about replacing one Windows PowerShell function with a different one.<\/span><\/p>\n<p>Microsoft Scripting Guy, Ed Wilson, is here. One of the problems with writing Windows PowerShell code every day is that you eventually&#8230;<\/p>\n<p>No, it\u2019s not what you think.<\/p>\n<p>I am often asked if I run out of ideas, and the answer is, &#8220;No, I don\u2019t.&#8221;<\/p>\n<p>There is so much I can do with Windows PowerShell that, in fact, a new version of Windows PowerShell will be out before I ever run out of ideas of what to do with the last version. No, the problem is not that I run out of ideas, but it is that I often forget what I have written. In fact, this week is a case in point.<\/p>\n<h2>Code cleanup<\/h2>\n<p>I wrote a cool function that creates a Windows PowerShell transcript log file name from the date. In it, I replace invalid file-name characters. Here is the function:<\/p>\n<p style=\"margin-left:30px\">\n  Function Get-TranscriptName\n<\/p>\n<p style=\"margin-left:30px\">\n  {\n<\/p>\n<p style=\"margin-left:30px\">\n  \u00a0$invalidChars = [io.path]::GetInvalidFileNamechars()\n<\/p>\n<p style=\"margin-left:30px\">\n  \u00a0$date = Get-Date -format s\n<\/p>\n<p style=\"margin-left:30px\">\n  \u00a0 &#8220;{0}.{1}.{2}.txt&#8221; -f &#8220;PowerShell_Transcript&#8221;, $env:COMPUTERNAME,\n<\/p>\n<p style=\"margin-left:30px\">\n  \u00a0 ($date.ToString() -replace &#8220;[$invalidChars]&#8221;,&#8221;-&#8220;) }\n<\/p>\n<p>There is absolutely nothing in the world wrong with it. It works fine (that is, if I don\u2019t start Windows PowerShell twice in the same second, which on my laptop is virtually impossible to do).<\/p>\n<p>The problem is that I have already written a cool Windows PowerShell function that will remove invalid characters from the file name. So THAT is why I knew about <strong>[io.path]::GetInvalidFileNameChars<\/strong>. I didn\u2019t even have to look it up\u2014I remembered it because I have used it before. Here is that function:<\/p>\n<p style=\"margin-left:30px\">\n  Function Replace-InvalidFileCharacters\n<\/p>\n<p style=\"margin-left:30px\">\n  {\n<\/p>\n<p style=\"margin-left:30px\">\n  \u00a0Param ($stringIn,\n<\/p>\n<p style=\"margin-left:30px\">\n  \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 $replacementChar)\n<\/p>\n<p style=\"margin-left:30px\">\n  \u00a0# Replace-InvalidFileCharacters &#8220;my?string&#8221;\n<\/p>\n<p style=\"margin-left:30px\">\n  \u00a0# Replace-InvalidFileCharacters (get-date).tostring()\n<\/p>\n<p style=\"margin-left:30px\">\n  \u00a0$stringIN -replace &#8220;[$( [System.IO.Path]::GetInvalidFileNameChars() )]&#8221;, $replacementChar\n<\/p>\n<p style=\"margin-left:30px\">\n  }\n<\/p>\n<p>It is obvious, that the code is essentially the same, but it has the added advantage that I can use it for any string, not just for the date. So, I decided that I want to add this function to my Windows PowerShell profile and cleanup the Windows PowerShell transcript name function a bit. But how?<\/p>\n<p>How do I bring in this function and cleanup the other function, when the <strong>Get-TranscriptName<\/strong> function has the functionality embedded into a rather complex line of Windows PowerShell code?<\/p>\n<h2>Add them to a blank page in the ISE<\/h2>\n<p>The answer is pretty simple:<\/p>\n<p>I open a new tab in the Windows PowerShell ISE, and I test it out. First I open my Windows PowerShell profile from the Windows PowerShell console by using my function <a href=\"https:\/\/devblogs.microsoft.com\/scripting\/i-have-a-powershell-profilenow-what\/\">Edit-Profile Windows PowerShell<\/a>.<\/p>\n<p>Then I add a new blank page to the Windows PowerShell ISE, and I copy the <strong>Get-TranscriptName<\/strong> and the <strong>Replace-InvalidFileCharacters<\/strong> functions to it. This is shown here:<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/29\/2019\/02\/Hsg-7-25-15-01.png\"><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/29\/2019\/02\/Hsg-7-25-15-01.png\" alt=\"Image of code\" title=\"Image of code\" \/><\/a><\/p>\n<h2>Reduce duplication<\/h2>\n<p>Now I need to look at what the <strong>Replace-InvalidFileCharacters<\/strong> function does and what I need to have in the <strong>Get-TranscriptName<\/strong> function. It is obvious that the key to both functions is the call to <strong>GetInvalidFileNameChars<\/strong> static method. So, I can comment out the first line in the **Get-TranscriptName **function:<\/p>\n<p style=\"margin-left:30px\">\n  Function Get-TranscriptName\n<\/p>\n<p style=\"margin-left:30px\">\n  {\n<\/p>\n<p style=\"margin-left:30px\">\n  \u00a0#$invalidChars = [io.path]::GetInvalidFileNamechars()\n<\/p>\n<p style=\"margin-left:30px\">\n  \u00a0$date = Get-Date -format s\n<\/p>\n<p style=\"margin-left:30px\">\n  \u00a0 &#8220;{0}.{1}.{2}.txt&#8221; -f &#8220;PowerShell_Transcript&#8221;, $env:COMPUTERNAME,\n<\/p>\n<p style=\"margin-left:30px\">\n  \u00a0 ($date.ToString() -replace &#8220;[$invalidChars]&#8221;,&#8221;-&#8220;) }\n<\/p>\n<p>The next thing that the <strong>Replace-InvalidFileCharacters<\/strong> function does is call the <strong>\u2013Replace<\/strong> operator to replace the invalid characters with the character supplied when the function is called. So I can also replace that line in my <strong>Get-TranscriptName<\/strong> function:<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/29\/2019\/02\/Hsg-7-25-15-02.png\"><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/29\/2019\/02\/Hsg-7-25-15-02.png\" alt=\"Image of code\" title=\"Image of code\" \/><\/a><\/p>\n<p>In the previous image, you can see that the two commands are essentially the same:<\/p>\n<p style=\"margin-left:30px\">\n  -Replace (characters to replace), (character to replace with)\n<\/p>\n<p>Because the <strong>Replace-InvalidFileCharacters<\/strong> function performs the same thing as the code highlighted in the <strong>Get-TranscriptName<\/strong>, I can substitute the code in the <strong>Get-TranscriptName<\/strong> function with a call to the <strong>Replace-InvalidFileCharacters<\/strong> function.<\/p>\n<p>I need to first load the <strong>Replace-InvalidFileCharacters<\/strong> function into memory in my Windows PowerShell ISE so I can test it to make sure everything works properly.<\/p>\n<p>The other thing I need to realize is that the <strong>(Get-Date).tostring()<\/strong> portion in my <strong>Get-TranscriptFileName<\/strong> function will need to be supplied as input to the <strong>Replace-InvalidFileCharacters<\/strong> function. So, here is the replacement:<\/p>\n<p style=\"margin-left:30px\">\n  Function Get-TranscriptName\n<\/p>\n<p style=\"margin-left:30px\">\n  {\n<\/p>\n<p style=\"margin-left:30px\">\n  \u00a0$date = Get-Date -format s\n<\/p>\n<p style=\"margin-left:30px\">\n  \u00a0 &#8220;{0}.{1}.{2}.txt&#8221; -f &#8220;PowerShell_Transcript&#8221;, $env:COMPUTERNAME,\n<\/p>\n<p style=\"margin-left:30px\">\n  \u00a0 (Replace-InvalidFileCharacters -stringIn $date.ToString() -replacementChar &#8220;-&#8220;) }\n<\/p>\n<p>Now, in keeping with my tenant that I like to create aliases for my functions, I create a new alias for <strong>Replace-InvalidFileCharacters<\/strong> that will be easier to type. I decide to see if <strong>RIFC<\/strong> is taken. I test it with <strong>Get-Alias<\/strong>:<\/p>\n<p style=\"margin-left:30px\">\n  PS C:\\> Get-Alias rifc\n<\/p>\n<p style=\"margin-left:30px\">\n  Get-Alias : This command cannot find a matching alias because an alias with the name &#8216;rifc&#8217; does not exist.\n<\/p>\n<p style=\"margin-left:30px\">\n  At line:1 char:1\n<\/p>\n<p style=\"margin-left:30px\">\n  + Get-Alias rifc\n<\/p>\n<p style=\"margin-left:30px\">\n  + ~~~~~~~~~~~~~~\n<\/p>\n<p style=\"margin-left:30px\">\n  \u00a0\u00a0\u00a0 + CategoryInfo\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 : ObjectNotFound: (rifc:String) [Get-Alias], ItemNotFoundException\n<\/p>\n<p style=\"margin-left:30px\">\n  \u00a0\u00a0\u00a0 + FullyQualifiedErrorId : ItemNotFoundException,Microsoft.PowerShell.Commands.GetAliasCommand\n<\/p>\n<p>It appears to be available, so I add this alias:<\/p>\n<p style=\"margin-left:30px\">\n  Set-Alias -Name rifc -Value Replace-InvalidFileCharacters | out-null\n<\/p>\n<p>Then I copy the two functions to my Windows PowerShell profile after I replace the long command name. The complete profile is shown here:<\/p>\n<p style=\"margin-left:30px\">\n  #&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;\n<\/p>\n<p style=\"margin-left:30px\">\n  #\n<\/p>\n<p style=\"margin-left:30px\">\n  # PowerShell console profile\n<\/p>\n<p style=\"margin-left:30px\">\n  # ed wilson, msft\n<\/p>\n<p style=\"margin-left:30px\">\n  #\n<\/p>\n<p style=\"margin-left:30px\">\n  # NOTES: contains five types of things: aliases, functions, psdrives,\n<\/p>\n<p style=\"margin-left:30px\">\n  # variables and commands.\n<\/p>\n<p style=\"margin-left:30px\">\n  # version 1.1\n<\/p>\n<p style=\"margin-left:30px\">\n  # 7\/20\/2015\n<\/p>\n<p style=\"margin-left:30px\">\n  # HSG 7-23-2015\n<\/p>\n<p style=\"margin-left:30px\">\n  #&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;\n<\/p>\n<p style=\"margin-left:30px\">\n  #Aliases\n<\/p>\n<p style=\"margin-left:30px\">\n  Set-Alias -Name ep -Value edit-profile | out-null\n<\/p>\n<p style=\"margin-left:30px\">\n  Set-Alias -Name tch -Value Test-ConsoleHost | out-null\n<\/p>\n<p style=\"margin-left:30px\">\n  Set-Alias -Name gfl -Value Get-ForwardLink | out-null\n<\/p>\n<p style=\"margin-left:30px\">\n  Set-Alias -Name gwp -Value Get-WebPage | out-null\n<\/p>\n<p style=\"margin-left:30px\">\n  Set-Alias -Name rifc -Value Replace-InvalidFileCharacters | out-null\n<\/p>\n<p style=\"margin-left:30px\">\n  #Functions\n<\/p>\n<p style=\"margin-left:30px\">\n  Function Edit-Profile\n<\/p>\n<p style=\"margin-left:30px\">\n  { ISE $profile }\n<\/p>\n<p style=\"margin-left:30px\">\n  Function Test-ConsoleHost\n<\/p>\n<p style=\"margin-left:30px\">\n  {\n<\/p>\n<p style=\"margin-left:30px\">\n  \u00a0if(($host.Name -match &#8216;consolehost&#8217;)) {$true}\n<\/p>\n<p style=\"margin-left:30px\">\n  \u00a0Else {$false}\u00a0\u00a0\n<\/p>\n<p style=\"margin-left:30px\">\n  }\n<\/p>\n<p style=\"margin-left:30px\">\n  Function Replace-InvalidFileCharacters\n<\/p>\n<p style=\"margin-left:30px\">\n  {\n<\/p>\n<p style=\"margin-left:30px\">\n  \u00a0Param ($stringIn,\n<\/p>\n<p style=\"margin-left:30px\">\n  \u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 $replacementChar)\n<\/p>\n<p style=\"margin-left:30px\">\n  \u00a0# Replace-InvalidFileCharacters &#8220;my?string&#8221;\n<\/p>\n<p style=\"margin-left:30px\">\n  \u00a0# Replace-InvalidFileCharacters (get-date).tostring()\n<\/p>\n<p style=\"margin-left:30px\">\n  \u00a0$stringIN -replace &#8220;[$( [System.IO.Path]::GetInvalidFileNameChars() )]&#8221;, $replacementChar\n<\/p>\n<p style=\"margin-left:30px\">\n  }\n<\/p>\n<p style=\"margin-left:30px\">\n  Function Get-TranscriptName\n<\/p>\n<p style=\"margin-left:30px\">\n  {\n<\/p>\n<p style=\"margin-left:30px\">\n  \u00a0$date = Get-Date -format s\n<\/p>\n<p style=\"margin-left:30px\">\n  \u00a0 &#8220;{0}.{1}.{2}.txt&#8221; -f &#8220;PowerShell_Transcript&#8221;, $env:COMPUTERNAME,\n<\/p>\n<p style=\"margin-left:30px\">\n  \u00a0 (rifc -stringIn $date.ToString() -replacementChar &#8220;-&#8220;) }\n<\/p>\n<p style=\"margin-left:30px\">\n  Function Get-WebPage\n<\/p>\n<p style=\"margin-left:30px\">\n  {\n<\/p>\n<p style=\"margin-left:30px\">\n  \u00a0Param($url)\n<\/p>\n<p style=\"margin-left:30px\">\n  \u00a0# Get-WebPage -url (Get-CmdletFwLink get-process)\n<\/p>\n<p style=\"margin-left:30px\">\n  \u00a0(New-Object -ComObject shell.application).open($url)\n<\/p>\n<p style=\"margin-left:30px\">\n  }\n<\/p>\n<p style=\"margin-left:30px\">\n  Function Get-ForwardLink\n<\/p>\n<p style=\"margin-left:30px\">\n  {\n<\/p>\n<p style=\"margin-left:30px\">\n  \u00a0Param($cmdletName)\n<\/p>\n<p style=\"margin-left:30px\">\n  \u00a0# Get-WebPage -url (Get-CmdletFwLink get-process)\n<\/p>\n<p style=\"margin-left:30px\">\n  \u00a0(Get-Command $cmdletName).helpuri\n<\/p>\n<p style=\"margin-left:30px\">\n  }\n<\/p>\n<p style=\"margin-left:30px\">\n  #Variables\n<\/p>\n<p style=\"margin-left:30px\">\n  New-Variable -Name doc -Value &#8220;$home\\documents&#8221;\n<\/p>\n<p style=\"margin-left:30px\">\n  #PS_Drives\n<\/p>\n<p style=\"margin-left:30px\">\n  New-PSDrive -Name Mod -Root ($env:PSModulePath -split &#8216;;&#8217;)[0] `\n<\/p>\n<p style=\"margin-left:30px\">\n  \u00a0-PSProvider FileSystem | out-null\n<\/p>\n<p style=\"margin-left:30px\">\n  #Commands\n<\/p>\n<p style=\"margin-left:30px\">\n  Set-Location c:\\\n<\/p>\n<p style=\"margin-left:30px\">\n  If(tch) {Start-Transcript -Path (Join-Path -Path `\n<\/p>\n<p style=\"margin-left:30px\">\n  \u00a0$doc -ChildPath $(Get-TranscriptName))}\n<\/p>\n<p>On Monday, I&#8217;ll wrap up Profile Week, and I&#8217;ll be talking about adding variable descriptions to my profile. I invite you to follow me on <a href=\"http:\/\/bit.ly\/scriptingguystwitter\" target=\"_blank\" rel=\"noopener\">Twitter<\/a> and <a href=\"http:\/\/bit.ly\/scriptingguysfacebook\" target=\"_blank\" rel=\"noopener\">Facebook<\/a>. If you have any questions, send email to me at <a href=\"mailto:scripter@microsoft.com\" target=\"_blank\" rel=\"noopener\">scripter@microsoft.com<\/a>, or post your questions on the <a href=\"http:\/\/bit.ly\/scriptingforum\" target=\"_blank\" rel=\"noopener\">Official Scripting Guys Forum<\/a>. See you tomorrow. Until then, peace.<\/p>\n<p><strong>Ed Wilson, Microsoft Scripting Guy<\/strong><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Summary: Ed Wilson, Microsoft Scripting Guy, talks about replacing one Windows PowerShell function with a different one. Microsoft Scripting Guy, Ed Wilson, is here. One of the problems with writing Windows PowerShell code every day is that you eventually&#8230; No, it\u2019s not what you think. I am often asked if I run out of ideas, [&hellip;]<\/p>\n","protected":false},"author":596,"featured_media":87096,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[69,51,144,3,4,61,45],"class_list":["post-71942","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-scripting","tag-functions","tag-getting-started","tag-profiles","tag-scripting-guy","tag-scripting-techniques","tag-weekend-scripter","tag-windows-powershell"],"acf":[],"blog_post_summary":"<p>Summary: Ed Wilson, Microsoft Scripting Guy, talks about replacing one Windows PowerShell function with a different one. Microsoft Scripting Guy, Ed Wilson, is here. One of the problems with writing Windows PowerShell code every day is that you eventually&#8230; No, it\u2019s not what you think. I am often asked if I run out of ideas, [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/posts\/71942","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/users\/596"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/comments?post=71942"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/posts\/71942\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/media\/87096"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/media?parent=71942"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/categories?post=71942"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/tags?post=71942"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}