<?xml version="1.0" encoding="utf-8"?>
<rss xmlns:atom="https://www.w3.org/2005/Atom" xmlns:sy="https://purl.org/rss/1.0/modules/syndication/" xmlns:dc="https://purl.org/dc/elements/1.1/" xmlns:content="https://purl.org/rss/1.0/modules/content/" xmlns:wfw="https://wellformedweb.org/CommentAPI/" xmlns:slash="https://purl.org/rss/1.0/modules/slash/"
  version="2.0">
  <channel>
    <title>Jan-Hendrik Peters’ blog</title>
    <atom:link
      href="https://docs.microsoft.com/archive/blogs/japete/feed.xml"
      rel="self"
      type="application/rss+xml" />
    <link>https://docs.microsoft.com/archive/blogs/japete/feed.xml</link>
    <description>My observations on PowerShell, Performance and Cloud Data Center topics</description>
    <lastBuildDate>Wed, 17 Apr 2019 03:44:35 GMT</lastBuildDate>
    <language>en-US</language>
    <sy:updatePeriod>hourly</sy:updatePeriod>
    <sy:updateFrequency>1</sy:updateFrequency>
    <item>
      <title>Wrap your PowerShell scripts as Executables</title>
      <link>https://docs.microsoft.com/archive/blogs/japete/wrap-your-powershell-scripts-as-executables</link>
      <pubDate>Mon, 11 Sep 2017 08:00:24 GMT</pubDate>
      <dc:creator><![CDATA[Jan-Hendrik Peters]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.technet.microsoft.com/japete/?p=25</guid>
      <description><![CDATA[Every now and then while delivering one of our PowerShell workshops, my customers ask me "How can I...]]></description>
      <content:encoded><![CDATA[Every now and then while delivering one of our PowerShell workshops, my customers ask me "How can I create an exe from my PowerShell script?" And every time the same reasoning is being used: To obfuscate the use of clear text passwords, connection strings and other sensitive data from prying eyes. Sure, most will tell me "The user should not see the script running" but this never seems to be the real reason :)<!--more-->

While I hopefully won't have to tell you, my esteemed readers, the importance of not storing sensitive data in scripts at all, I still wanted to share my PowerShell wrapper (<a target="_blank" href="https://github.com/nyanhp/exewrapper" rel="noopener noreferrer">GitHub repo</a>). After all, there might be reasons to use this like creating a transport vessel for your script and it's dependencies that even executes itself without having to be extracted before.

In order to do this I am leveraging the awesome capabilities of PowerShell, allowing me to locate the compiler at runtime and compiling my wrapper code into an executable. So let's examine this step by step.

First of all, the script content is read via Get-Content. This string will be placed in your .NET code later on, so we need to make sure that all weird characters are properly escaped.
<pre>$scriptContent = (Get-Content $ScriptPath -Raw -Encoding UTF8) -replace "\\", "\\" -replace "`r`n", "\n" -replace '"', '\"'
</pre>
The next order of business is to create a temporary file (*.cs) which we can actually compile later on. Easy - the .NET framework helps us with the Path class and it's static methods:
<pre>$temp = [System.IO.Path]::GetTempFileName() -replace "\.tmp", ".cs"</pre>
Check out the other static members of System.IO.Path by applying a little Get-Member magic:
<pre>[System.IO.Path] | Get-Member -Static</pre>
These methods will help you in many circumstances - just as a little sidenote.

We set the script contents to our actual .NET code - I would like to stress here that this is a very, very simple piece of code which does not take into account: Interactive scripts, scripts throwing errors like there is no tomorrow, return values that you might need, ... You might use it more as a base to build upon.
<div>
<pre><span>@"</span>
<span>using System;</span>
<span>using System.Management.Automation;</span>

<span>namespace POSHRocks</span>
<span>{</span>
<span>    public class Wrapper</span>
<span>    {</span>
        <span>public static void Main(string[] args)</span>
<span>        {</span>
            <span>PowerShell ps = PowerShell.Create();</span>
<span>            ps.Commands.AddScript("</span><span>$</span><span>scriptContent</span><span>");</span>
<span>            ps.Invoke();</span>
<span>        }</span>
<span>    }</span>
<span>}</span>
<span>"@</span><span> </span><span>|</span><span> </span><span>Out-File</span><span> </span><span>$</span><span>temp</span></pre>
<div>This is all fine and dandy - and all of this should not be new to you if you have been using PowerShell for a little while. Observe the use of the here-string because I am too lazy to use the backtick to escape my quotation marks.</div>
<div></div>
<div>Next up: How will my code become an executable? Usually this is some black magic happening behind the scenes when you - the developer - hit "Build project" in Visual Studio. What actually happens at some point is: The C# compiler, csc.exe, will be called to build your assembly with all it's additional files, references and so on. For more information, check out <a target="_blank" href="https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/compiler-options/index" rel="noopener noreferrer">https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/compiler-options/index</a>.</div>
<div></div>
<div>To locate our compiler location we can make use of .NET once again:</div>
<div>
<div>
<pre><span>$</span><span>compiler</span><span> </span><span>= </span><span>Join-Path</span><span> </span><span>-</span><span>Path </span><span>(</span><span>[Runtime.InteropServices.RuntimeEnvironment]</span><span>::GetRuntimeDirectory</span><span>()</span><span> </span><span>)</span><span> </span><span>-</span><span>ChildPath </span><span>csc.exe</span></pre>
<div>We basically locate the .NET runtime folder and assume that we can find the compiler there as well.</div>
<div></div>
<div>If you took a look at the linked article a couple of lines back you also saw the command-line options for our C# compiler. We use these to kick off a new process that will compile our script for us. Notice in the command-line options that the referenced assembly can also be easily found. Since we need to add a reference to the namespace System.Management.Automation, we can simply make use of reflection and get the Assembly location of a psobject, the one object inherently linked with PowerShell and the S.M.A namespace.</div>
<div>
<div>
<pre><span># Compile the exe</span>
<span>$</span><span>arguments</span><span> </span><span>=</span><span> </span><span>@(</span>
    <span>"/target:exe"</span>
    <span>"/out:</span><span>$</span><span>ExePath</span><span>"</span>
    <span>"/r:`"</span><span>$(</span><span>[psobject]</span><span>.Assembly.Location</span><span>)</span><span>`""</span>
    <span>$</span><span>temp</span>
<span>)</span>
<span>$</span><span>compilation</span><span> </span><span>=</span><span> </span><span>Start-Process</span><span> </span><span>-</span><span>FilePath </span><span>$</span><span>compiler</span><span> </span><span>-</span><span>ArgumentList </span><span>$</span><span>arguments</span><span> </span><span>-</span><span>Wait </span><span>-</span><span>NoNewWindow </span><span>-</span><span>PassThru</span></pre>
<div>Afterwards we check for success as defined by the property ExitCode of $compilation and return the FileInfo object of our new executable.</div>
<div>So there you go, creating an executable from a script (or any source code for that matter) from the shell, while dynamically retrieving our compiler and references.</div>
<div></div>
</div>
<div>Oh, and one little caveat: Remember me telling you not to store passwords in your scripts? This happens if you don't obey:</div>
<div><img width="175" height="112" class="alignnone size-full wp-image-46" alt="Namespace as seen in disassembler pointing to POSHRocks.Wrapper" src="https://msdnshared.blob.core.windows.net/media/2017/09/BlogPost1.png" /></div>
<div><a href="https://msdnshared.blob.core.windows.net/media/2017/09/BlogPost21.png"><img width="1024" height="143" class="alignnone size-large wp-image-65" alt="The disassembled .NET code showing a clear-text password" src="https://msdnshared.blob.core.windows.net/media/2017/09/BlogPost21-1024x143.png" /></a></div>
</div>
</div>
</div>
</div>]]></content:encoded>
    </item>
    <item>
      <title>Remotely find missing updates with an offline scan file</title>
      <link>https://docs.microsoft.com/archive/blogs/japete/remotely-find-missing-updates-with-an-offline-scan-file</link>
      <pubDate>Mon, 04 Sep 2017 13:19:02 GMT</pubDate>
      <dc:creator><![CDATA[Jan-Hendrik Peters]]></dc:creator>
      <guid
        isPermaLink="false">https://blogs.technet.microsoft.com/japete/?p=35</guid>
      <description><![CDATA[Part of our Microsoft Baseline Security Analyzer offering is retrieving a list of missing security...]]></description>
      <content:encoded><![CDATA[Part of our Microsoft Baseline Security Analyzer offering is retrieving a list of missing security updates. Today I would like to share the script to remotely do this in your infrastructure with you.<!--more-->The first step before this script can be executed is downloading the actual cab file that can be used to scan for updates from here: <a target="_blank" href="https://support.microsoft.com/en-us/help/926464/a-new-version-of-the-windows-update-offline-scan-file--wsusscn2-cab--i" rel="noopener noreferrer">https://support.microsoft.com/en-us/help/926464/a-new-version-of-the-windows-update-offline-scan-file--wsusscn2-cab--i</a>. The script which we will examine closer will attempt an automatic download of the cab file.

The second step is downloading the actual script itself, which is hosted on GitHub: <a target="_blank" href="https://github.com/nyanhp/GetMissingUpdates" rel="noopener noreferrer">https://github.com/nyanhp/GetMissingUpdates</a>

Let's go over this simple script step by step.
<pre>param
(
    [Parameter(Mandatory = $true)]
    [System.String[]]
    $ComputerName,

    [Parameter(Mandatory = $true, ParameterSetName = 'Path')]
    [System.String]
    $Path,

    # As of Sep 2017 https://go.microsoft.com/fwlink/?linkid=74689
    [Parameter(Mandatory = $true, ParameterSetName = 'Url')]
    [System.String]
    $DownloadUri,

    [Parameter()]
    [System.String]
    $UpdateSearchFilter = 'IsHidden = 0',

    [Parameter()]
    [pscredential]
    $Credential
)</pre>
The parameters should be quite clear. A list of computer names to connect to, either the source path or the download link to the wsusscn2.cab, the update search filter as well as a credential in case Kerberos is not available. If the parameter set 'Url' is chosen, the script will attempt to download the cab file to [System.IO.Path]::GetTempPath().

In general, the script will copy the wsusscn2.cab to all targets first. How long this step takes depends entirely on you: If the administrative share can be accessed, we will use Copy-Item. If it is not available, but the source system has at least WMF5.0 installed and the target system is running at least WMF3 we will make use of the new ToSession parameter of Copy-Item. If all else fails, we will use Lee Holmes' methods Send-File and Write-File to stream the file via WinRM to our targets. These methods range from fastest to slowest in this order.
<pre>try
{
    $osRoot = Invoke-Command -Session $session -ScriptBlock { $env:SystemDrive } -ErrorAction Stop
}
catch
{
    Write-Host ('Error retrieving OS root path from {0}. Assuming issue with the connection. Error was {1}' -f $computer, $_.Exception.Message)
    Write-Error -Message ('Error retrieving OS root path from {0}. Assuming issue with the connection. Error was {1}' -f $computer, $_.Exception.Message)
}

try
{
    $osPSVersion = Invoke-Command -Session $session -ScriptBlock { $PSVersionTable.PSVersion.Major } -ErrorAction Stop
}
 catch
{
    Write-Host ('Error retrieving OS Powershell version from {0}. Assuming issue with the connection. Error was {1}' -f $computer, $_.Exception.Message)
    Write-Error -Message ('Error retrieving OS Powershell version from {0}. Assuming issue with the connection. Error was {1}' -f $computer, $_.Exception.Message)
}

$adminShare = '\\{0}\{1}$' -f $computer, ($osRoot -replace '[:\\]')
$useSmb = Test-Path $adminShare

$destination = (Join-Path -Path $osRoot -ChildPath wsusscn2.cab)</pre>
On the target, a script block is executed. In this block, we instanciate the COM classes Microsoft.Update.Session and Microsoft.Update.ServiceManager and make use of a couple of methods to initiate the offline scan.
First of all, an UpdateManager object is needed that knows the offline scan cab file:
<pre>$UpdateService = $UpdateServiceManager.AddScanPackageService("Offline Sync Service", $Destination)</pre>
Afterwards an UpdateSearcher will be created with the offline scan repository. Notice here the ServiceID which is set to the GUID of the updater service. The server selection value of three classifies this as an unmanaged server ( <a target="_blank" href="https://msdn.microsoft.com/en-us/library/windows/desktop/aa387280(v=vs.85).aspx" rel="noopener noreferrer">https://msdn.microsoft.com/en-us/library/windows/desktop/aa387280(v=vs.85).aspx</a> ).
<pre>$UpdateSearcher = $UpdateSession.CreateUpdateSearcher()
<span>$</span><span>UpdateSearcher</span><span>.ServerSelection</span><span> </span><span>=</span><span> </span><span>3
</span><span>$</span><span>UpdateSearcher</span><span>.ServiceID</span><span> </span><span>=</span><span> </span><span>$</span><span>UpdateService</span><span>.ServiceID
</span></pre>
Lastly, the updates are located by using a search filter:
<div>
<pre><span>$</span><span>SearchResult</span><span> </span><span>=</span><span> </span><span>$</span><span>UpdateSearcher</span><span>.Search</span><span>($</span><span>UpdateSearchFilter</span><span>)</span></pre>
<div>All results are retrieved and will be returned from the script. All in all, you can simply use the script like this:</div>
<pre>.\GetMissingUpdates.ps1 -ComputerName serverA,serverB,serverC -Path D:\wsusscn2.cab -Credential (Get-Credential) -Verbose</pre>
Thanks for taking your time and reading all the way to the bottom and enjoy!

</div>]]></content:encoded>
    </item>
  </channel>
</rss>