{"id":1706,"date":"2014-04-01T00:01:00","date_gmt":"2014-04-01T00:01:00","guid":{"rendered":"https:\/\/blogs.technet.microsoft.com\/heyscriptingguy\/2014\/04\/01\/build-constrained-powershell-endpoint-using-startup-script\/"},"modified":"2014-04-01T00:01:00","modified_gmt":"2014-04-01T00:01:00","slug":"build-constrained-powershell-endpoint-using-startup-script","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/scripting\/build-constrained-powershell-endpoint-using-startup-script\/","title":{"rendered":"Build Constrained PowerShell Endpoint Using Startup Script"},"content":{"rendered":"<p><b>Summary<\/b>: Learn how to build a constrained endpoint by using a startup script.<\/p>\n<p>Hey, Scripting Guy!, how do I lock-down my remote endpoint to only certain commands?<\/p>\n<p>&mdash;GH<\/p>\n<p>Honorary Scripting Guy, Boe Prox, here today filling in for my good friend, The Scripting Guy. This is the second part in a series of five posts about Remoting Endpoints. The series includes:<\/p>\n<ol>\n<li><a href=\"https:\/\/devblogs.microsoft.com\/scripting\/introduction-to-powershell-endpoints\/\" target=\"_blank\">Introduction to PowerShell Endpoints<\/a><\/li>\n<li><strong>Build Constrained PowerShell Endpoint Using Startup Script<\/strong> (today&rsquo;s post)<\/li>\n<li>Build Constrained PowerShell Endpoint Using Configuration File<\/li>\n<li>Use Delegated Administration and Proxy Functions<\/li>\n<li>Build a Tool that Uses Constrained PowerShell Endpoint<\/li>\n<\/ol>\n<p>As seen at the end of yesterday&rsquo;s post, I was able to restrict the commands that are available from a remote Windows PowerShell session by using a constrained endpoint. Today I am going to show you how to create a constrained endpoint by using a startup script.<\/p>\n<p>In Windows PowerShell&nbsp;2.0, this was the only way to configure constrained endpoints, but in Windows PowerShell&nbsp;4.0 and Windows PowerShell&nbsp;3.0, this is a more optional approach. By using <b>Register-PSSession<\/b> with the <b>&ndash;StartupScript<\/b> parameter, you can specify the startup script that will be used when a user makes a connection to the system via the constrained endpoint configuration.<\/p>\n<p>Configuring a startup script may seem tricky at first, and depending on your requirements, it may take a little bit of time to get everything locked down the way you want. But fear not! I will take you through the steps required to make all of this come together.<\/p>\n<p>Let&rsquo;s start by opening the ISE and giving the startup script a name similar to what we expect to name the session configuration. In this case, I am going to call it <b>ConstrainedSession.ps1<\/b>.<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/29\/2019\/02\/7673.StartupScript1.png\"><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/29\/2019\/02\/7673.StartupScript1.png\" alt=\"Image of tab\" title=\"Image of tab\" \/><\/a><\/p>\n<p>Now comes the fun part: writing the necessary code to lock down the session to only a few commands and perhaps a couple of other things. Be mindful to the order of things here because the startup script could potentially fail if you call one thing prior to another.<\/p>\n<p>I want to define the cmdlets that I want to have available for anyone who uses this constrained endpoint.<\/p>\n<p style=\"margin-left:30px\">#Define any visible cmdlets that we want visible to the user<\/p>\n<p style=\"margin-left:30px\">[string[]]$PublicCmdlets = &#039;Get-Process&#039;,&#039;Get-Service&#039;,&#039;Clear-Host&#039;,&#039;Get-Alias&#039;<\/p>\n<p>Nothing special here&mdash;I&rsquo;m simply setting up the white list so I know to keep these available for the users.<\/p>\n<p>Next up is a list of applications that I would also like to have available. These can include anything&mdash;such as <b>ping<\/b> or <b>ipconfig<\/b>.<\/p>\n<p style=\"margin-left:30px\">#Define applications that I want visible to the user; ie: ping, netstat, etc&#8230;<\/p>\n<p style=\"margin-left:30px\">[string[]]$Apps = &#039;ping&#039;,&#039;netstat&#039;,&#039;ipconfig&#039;<\/p>\n<p style=\"margin-left:30px\">[string[]]$PublicApps = ForEach ($app in $apps) {<\/p>\n<p style=\"margin-left:30px\">&nbsp;&nbsp;&nbsp; Get-Command $app | Select -ExpandProperty Definition<\/p>\n<p style=\"margin-left:30px\">}<\/p>\n<p>In this case, I am allowing the use of <b>ping, netstat<\/b>,<b> <\/b>and <b>ipconfig<\/b> in the endpoint. The key thing here is to make sure that you have the full path to the executable so it can be made available later in the script. If you do not use a full path, it will not work properly. Also, these are currently being saved as a <b>string[]<\/b> object, but later I will need to make an adjustment to properly load these into the session.<\/p>\n<p style=\"margin-left:30px\">$PublicApps<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/29\/2019\/02\/8233.StartupScript2.png\"><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/29\/2019\/02\/8233.StartupScript2.png\" alt=\"Image of command output\" title=\"Image of command output\" \/><\/a><\/p>\n<p>Like the previous two items, I want to keep a few aliases available. If I had other scripts that I would like to have had available, I would also add them.<\/p>\n<p style=\"margin-left:30px\">#Define any Public Aliases<\/p>\n<p style=\"margin-left:30px\">[string[]]$PublicAliases = &#039;gsv&#039;,&#039;gps&#039;,&#039;ps&#039;,&#039;exsn&#039;,&#039;cls&#039;,&#039;gal&#039;<\/p>\n<p style=\"margin-left:30px\">#Define Public Scripts<\/p>\n<p style=\"margin-left:30px\">[string[]]$PublicScripts = $Null<\/p>\n<p>Now we can start hiding the cmdlets, aliases, and variables. &nbsp;Only those cmdlets and aliases that we defined earlier as being public will still be visible during the constrained session.<\/p>\n<p>You might be asking why I am not keeping any variables publicly available. The answer to that is that it will not matter whether they are visible because we are running this session in a <b>NoLanguage<\/b> mode. This means that we cannot access any of the language features of Windows PowerShell to include variables.<\/p>\n<p style=\"margin-left:30px\">#Cmdlets<\/p>\n<p style=\"margin-left:30px\">Get-Command | ForEach {<\/p>\n<p style=\"margin-left:30px\">&nbsp;&nbsp;&nbsp; If ($PublicCmdlets -notcontains $_.Name) {<\/p>\n<p style=\"margin-left:30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $_.Visibility = &#039;Private&#039;<\/p>\n<p style=\"margin-left:30px\">&nbsp;&nbsp;&nbsp; }<\/p>\n<p style=\"margin-left:30px\">}<\/p>\n<p style=\"margin-left:30px\">#Aliases<\/p>\n<p style=\"margin-left:30px\">Get-Alias | ForEach {<\/p>\n<p style=\"margin-left:30px\">&nbsp;&nbsp;&nbsp; If ($PublicAliases -notcontains $_.Name) {<\/p>\n<p style=\"margin-left:30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $_.Visibility = &#039;Private&#039;<\/p>\n<p style=\"margin-left:30px\">&nbsp;&nbsp;&nbsp; }<\/p>\n<p style=\"margin-left:30px\">}<\/p>\n<p style=\"margin-left:30px\">#Variables<\/p>\n<p style=\"margin-left:30px\">Get-Variable | ForEach {<\/p>\n<p style=\"margin-left:30px\">&nbsp;&nbsp;&nbsp; $_.Visibility = &#039;Private&#039;<\/p>\n<p style=\"margin-left:30px\">}<\/p>\n<p>When the script starts, we need to make sure that the applications (such as ping.exe) and any scripts are wiped from the session state.<\/p>\n<p style=\"margin-left:30px\">$ExecutionContext.SessionState.Applications.Clear()<\/p>\n<p style=\"margin-left:30px\">$ExecutionContext.SessionState.Scripts.Clear()<\/p>\n<p>Now any defined applications and scripts can be added back into the session.&nbsp; I mentioned earlier that a <b>string[] <\/b>object would not be accepted at this point, so I have to cast the object as <b>System.Collections.Generic.IEnumerable[string]<\/b> so it works properly with the <b>AddRange()<\/b> method.<\/p>\n<p style=\"margin-left:30px\">If ($PublicApps) {<\/p>\n<p style=\"margin-left:30px\">&nbsp;&nbsp;&nbsp; $ExecutionContext.SessionState.Applications.AddRange(`<\/p>\n<p style=\"margin-left:30px\">&nbsp; &nbsp;&nbsp;($PublicApps -as [System.Collections.Generic.IEnumerable[string]]))<\/p>\n<p style=\"margin-left:30px\">}<\/p>\n<p style=\"margin-left:30px\">If ($PublicScripts) {<\/p>\n<p style=\"margin-left:30px\">&nbsp;&nbsp;&nbsp; $ExecutionContext.SessionState.Scripts.AddRange(`<\/p>\n<p style=\"margin-left:30px\">&nbsp;&nbsp;&nbsp; ($PublicScripts -as [System.Collections.Generic.IEnumerable[string]]))<\/p>\n<p style=\"margin-left:30px\">}<\/p>\n<p>Now we can set the language to <b>NoLanguage<\/b> to lock things down for the session.<\/p>\n<p style=\"margin-left:30px\">$ExecutionContext.SessionState.LanguageMode = &quot;NoLanguage&quot;<\/p>\n<p>I am going to create a default restricted remote server <b>SessionState<\/b> that I can use to determine what commands I need to keep to ensure that the constrained remote endpoint works properly.<\/p>\n<p style=\"margin-left:30px\">$SessionStateType = [System.Management.Automation.Runspaces.InitialSessionState]<\/p>\n<p style=\"margin-left:30px\">$SessionState = $SessionStateType::CreateRestricted(&quot;RemoteServer&quot;)<\/p>\n<p>Looking at the <b>$SessionState<\/b> object, we can see a number of things such as the Language of the restricted session (<b>NoLanguage<\/b>), and we can take a look at the commands available in this session. Speaking of commands, let&rsquo;s take a look at those commands and see if anything jumps out.<\/p>\n<p style=\"margin-left:30px\">$SessionState.Commands | Select Name, Visibility, CommandType<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/29\/2019\/02\/7103.3.png\"><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/29\/2019\/02\/7103.3.png\" alt=\"Image of command output\" title=\"Image of command output\" \/><\/a><\/p>\n<p>Of course, these aren&rsquo;t all of the commands, but you will notice that all of the cmdlets have their visibility set to <b>Private<\/b>, which prevents anyone under this restricted session from seeing or using the cmdlets. Now take a look at the last seven commands. They are the only public commands here, and they are labeled as functions. If their names seem familiar, it is because these are proxy functions of the actual cmdlets that were set to <b>Private<\/b>.<\/p>\n<p style=\"margin-left:30px\">($SessionState.Commands | Group Name | Where {<\/p>\n<p style=\"margin-left:30px\">&nbsp;&nbsp;&nbsp; $_.count -eq 2<\/p>\n<p style=\"margin-left:30px\">}).Group | Select Name, Visibility,CommandType<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/29\/2019\/02\/3113.4.png\"><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/29\/2019\/02\/3113.4.png\" alt=\"Image of command output\" title=\"Image of command output\" \/><\/a><\/p>\n<p>These are also the required commands for a working constrained endpoint. I need to hold on to these seven proxy functions for later use.<\/p>\n<p style=\"margin-left:30px\">$proxyfunctions = $SessionState.Commands | Where {<\/p>\n<p style=\"margin-left:30px\">&nbsp;&nbsp;&nbsp; $_.CommandType -eq &#039;Function&#039;<\/p>\n<p style=\"margin-left:30px\">}<\/p>\n<p>The last thing to do in this startup script is to add those proxy functions into the session;<\/p>\n<p style=\"margin-left:30px\">$proxyfunctions | ForEach {<\/p>\n<p style=\"margin-left:30px\">&nbsp;&nbsp;&nbsp; Set-Item &quot;Function:\\$($_.Name)&quot; $_.Definition<\/p>\n<p style=\"margin-left:30px\">}<\/p>\n<p>Great! Now we will save this script to a location that we know won&rsquo;t be modified or removed (saving to your profile is probably not a good idea). This way you can edit the script as necessary and not worry about re-registering the session configuration each time. It will automatically load the updated script each time someone connects to the endpoint!<\/p>\n<p>Now let&rsquo;s register the new session and attempt to connect to it to check things out. You will need the full path to the script for this to be successful.<\/p>\n<p style=\"margin-left:30px\">Register-PSSessionConfiguration -Name ConstrainedSession -StartupScript &quot;C:\\PSSessions\\ConstainedSession.ps1&quot;<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/29\/2019\/02\/0820.5.png\"><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/29\/2019\/02\/0820.5.png\" alt=\"Image of command output\" title=\"Image of command output\" \/><\/a><\/p>\n<p>I decided to allow the default groups (Administrators and Remote Management Users) access to this endpoint. If I wanted to allow more access, I can specify the <b>ShowSecurityDescriptorUI<\/b> parameter. This brings up the familiar Permissions window that we all know and love, and I can make adjustments as needed.<\/p>\n<p style=\"margin-left:30px\">Set-PSSessionConfiguration -Name ConstrainedSession -ShowSecurityDescriptorUI<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/29\/2019\/02\/2262.6.png\"><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/29\/2019\/02\/2262.6.png\" alt=\"Image of command output\" title=\"Image of command output\" \/><\/a><\/p>\n<p>I&rsquo;m not really interested in making changes at this point in time, so I will cancel this and continue forward.<\/p>\n<p>Now let&rsquo;s enter the session and see what we can and cannot do:<\/p>\n<p style=\"margin-left:30px\">Enter-PSSession -ComputerName $env:Computername -ConfigurationName ConstrainedSession<\/p>\n<p>Running various commands yields different results.<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/29\/2019\/02\/2100.7.png\"><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/29\/2019\/02\/2100.7.png\" alt=\"Image of command output\" title=\"Image of command output\" \/><\/a><\/p>\n<p>You will notice that only a handful of cmdlets are available and the proxy functions that I defined are available to use. <b>Ping<\/b> is available to use, but <b>nslookup<\/b> (which was not specified to be public) is not available. The usually available <b>$Pwd<\/b> variable has now been locked away from me to use. Even the aliases are locked down to only a few publicly available ones.<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/29\/2019\/02\/8270.8.png\"><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/29\/2019\/02\/8270.8.png\" alt=\"Image of command output\" title=\"Image of command output\" \/><\/a><\/p>\n<p>As you can see, using a startup script to help constrain an endpoint can be very useful (especially if your systems are still running Windows PowerShell&nbsp;2.0). Now wouldn&rsquo;t be great if there is a session configuration file that has all of this available to us and would make things much simpler to create and modify? Well, tomorrow&rsquo;s blog post will cover just that!<\/p>\n<p>GH, that is all there is to using a startup script to enable a constrained endpoint. Remoting Endpoint Week will continue tomorrow when I will talk about building a constrained endpoint by using a configuration file.<\/p>\n<p>I invite you to follow the Scripting Guys on <a href=\"http:\/\/bit.ly\/scriptingguystwitter\" target=\"_blank\">Twitter<\/a> and <a href=\"http:\/\/bit.ly\/scriptingguysfacebook\" target=\"_blank\">Facebook<\/a>. If you have any questions, send email to me at <a href=\"mailto:scripter@microsoft.com\" target=\"_blank\">scripter@microsoft.com<\/a>, or post your questions on the <a href=\"http:\/\/bit.ly\/scriptingforum\" target=\"_blank\">Official Scripting Guys Forum<\/a>. See you tomorrow.<\/p>\n<p><b>Boe Prox, Honorary Scripting Guy<\/b>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Summary: Learn how to build a constrained endpoint by using a startup script. Hey, Scripting Guy!, how do I lock-down my remote endpoint to only certain commands? &mdash;GH Honorary Scripting Guy, Boe Prox, here today filling in for my good friend, The Scripting Guy. This is the second part in a series of five posts [&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":[162,496,56,57,45],"class_list":["post-1706","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-scripting","tag-boe-prox","tag-endpoint","tag-guest-blogger","tag-remoting","tag-windows-powershell"],"acf":[],"blog_post_summary":"<p>Summary: Learn how to build a constrained endpoint by using a startup script. Hey, Scripting Guy!, how do I lock-down my remote endpoint to only certain commands? &mdash;GH Honorary Scripting Guy, Boe Prox, here today filling in for my good friend, The Scripting Guy. This is the second part in a series of five posts [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/posts\/1706","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=1706"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/posts\/1706\/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=1706"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/categories?post=1706"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/tags?post=1706"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}