create a function with proper scope - powershell

I need to create a script to authenticate to a remote PC and access to PS console. It actually works, but I need to execute a number of commands when I need to terminate this session.
So I create a function STOP to be called to terminate the session, but it can't be used.
Is this a problem of scope (because it's inside an if statement)? If I manually create the same function and then I try to call it... It works!
# get connection parameters
$credential = Get-Credential -Credential xxx
$remote_PC = Read-Host "Insert the server name or IP (Q to abort): "
# connect
if (someKindOfCheck) {
Enter-PSSession -ComputerName $remote_PC -Credential $credential
# some commands
function STOP {
Exit-PSSession;
# some commands
}
}

I've come across a similar issue before, and I resolved it by creating a New-PSSession and assigning it to a varialbe, I then use the Invoke-Command with -Session option to execute the required commands.

Enter-PSSession is only for interactive use (once you've entered that session it only takes input from the console).
The only way I can think of that would make this work "automagically" would be to create a custom remoting session on the target machine that implements a proxy function for Exit-PSSession, and put your commands in that function.

Related

Passing active powershell Session to background jobs

I am writing a powershell script to manipulate Exchange Online mailboxes.
I want this script to run with background jobs in parallel, so I'm trying to use PoshRSJobs (https://github.com/proxb/PoshRSJob) to create the jobs.
My code is:
Connect-ExchangeOnline -Credentials ...
Start-RSJob -ModulesToImport ExchangeOnlineManagement -Throttle $ProcesosConcurrentes -InputObject $jobs -ScriptBlock {
./migra_buzon.ps1 ...
}
Where:
$jobs is an arraylist where I have the parameter of the mailboxes I want to operate with
migra_buzon.ps1 is another powershell scripts that operates over one specified mailbox
The problem I have when I run this way is that in the jobs I have the error:
The term 'Add-MailboxPermission' is not recognized as a name of a cmdlet, function, script file, or executable program.
Check the spelling of the name, or if a path was included, verify that the path is correct and try again.
Although other commands like Get-EXOMailbox are working correctly.
Looking for help I found that the problem can be related with the session, so I changed my code to:
Connect-ExchangeOnline -Credentials ...
Start-RSJob -ModulesToImport ExchangeOnlineManagement -Throttle $ProcesosConcurrentes -InputObject $jobs -ScriptBlock {
$o365session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri "https://outlook.office365.com/powershell-liveid" -Credential $(Import-Clixml $Using:ExchangeCredentials) -Authentication "Basic" -AllowRedirection
Import-PSSession $o365Session -CommandName #('Add-MailboxPermission', 'Get-MailboxPermission')
./migra_buzon.ps1 ...
}
In this case, the problem I have is with the Exchange connection. After running a few jobs I'm getting the error:
[outlook.office365.com] Processing data from remote server outlook.office365.com failed with the following error message: Client did not get proper response from server. For more information, see the about_Remote_Troubleshooting Help topic.
Cannot validate argument on parameter 'Session'. The argument is null. Provide a valid value for the argument, and then try running the command again.
So my question is, what is the right way to run background jobs sharing the connection got in the main process?
Thanks
PS: I first tried to run jobs with Start-Job, but with this the problem is that each background job needs its own connection, so I got and maximum number of connections exceeded. And this is the reason I changed my code to Start-RSJob
It appears that you are hitting Exchange Online throttling limits.
If that indeed the case, you can try the following method.
How to relax PowerShell throttling
There is a relatively new customer facing way to increase or update PowerShell Throttling Policies.
Go to Microsoft 365 admin center.
Validate that you are logged in with the user that has the correct role assignment.
Click on the Need Help? Widget in the bottom right corner
Graphical user interface
Type Exchange PowerShell throttling in the search box and select “Temporarily update throttling policies for a migration”. Keep in mind that this is only applicable for 90 days. After 90 days, the throttles will return to back to the default values for that tenant.
MachSol offers Tenant management using a job engine, that allows you to do multiple operations using front-end and let the jobs handler take care of processing in background. You can give it a try:
https://www.machsol.com/machpanel-automation-for-microsoft-CSP-partners/

Powershell - Pass in and execute string without using WinRM

I am trying to utilize a custom PS function that will accept a powershell command as a string parameter and execute/store the results in a variable within the function itself to be referenced later. After doing some research, I found a way of doing this by passing in "Get-WinEvent" as a string variable to the function and then within the function converting that variable to a scriptblock like so:
$PowershellQueryScript = [Scriptblock]::Create($PowershellQuery)
Once I did that, I called the variable in my foreach loop and stored the results like so:
$myResults = Invoke-Command -ComputerName $server -ScriptBlock $PowershellQueryScript
Now the error I receive is in regards to WinRM not being enabled ("Connecting to remote server myServer failed with the following error message : WinRM cannot process the request.") and unfortunately I do not think I will be allowed to make this settings change in production. If I were to call this command directly from my server -> production without utilizing Invoke-Command then everything works as expected as I think it does not have to create a remote PS session on the remote server so WinRM is not required.
Are there any other workarounds for allowing a user to call my function with a string and have that string be executed in the function itself WITHOUT having to enable WinRM?
After some more research, I found out WinRM was already enabled and the proper firewall settings were already configured as well. The problem was that I was using cnames and not FQDNs (Fully Qualified Domain Names) so the DNS was not resolving properly. Issue is resolved by converting cname to a value that can be resolved:
$ServerFQDN = [System.Net.Dns]::GetHostEntry([string]$ServerCname).HostName

PowerShell Get-AppVServerPackage behaves differently when executed in a remote session

Good Morning all,
i have a slight problem with the remote execution of the "Get-AppVServerPackage" PowerShell Applet.
We try to receive all the information available for all packages from a remote location.
When executing "Get-AppVServerPackage" on the AppV server, i get:
the information about the contained applications.
[1] https://i.stack.imgur.com/pIx4m.png
When i user Enter-PSSession from a PowerShell command line, i get
[2] https://i.stack.imgur.com/GvEtc.png
When i try to use it in a script supposed to run unattended, i get
[3] https://i.stack.imgur.com/m8Ddf.png
So i tried to get more information about "Microsoft.AppV.Server.AppVMgmtDataTypes.Application"
$s = New-PSSession -computerName myAppVServer -Authentication Kerberos
$test = Invoke-Command -Session $s -ScriptBlock { Get-AppVServerPackage }
$test.Applications[0] |gm
will give me a long list of Methods, and only one Property "Length".
Can anyone tell me how i can get the information about the application? It works with the
$test.Entitlements
will give me a result i can work with.
Thanks a lot!
Holger
Sometimes you get lucky,
replacing
{ Get-AppVServerPackage }
with
{ Get-AppVServerPackage | Select ID, Name, Description, PackageGuid, VersionGuid, Applications, Entitlements }
solved the problem.

Powershell "screen" - keep the processes running even the connection is dropped?

I'm using enter-pssession to run scripts on remote servers. So I can login remotely to the servers. Run commands interactively, close the powershell console and later I can reattach the session and check the commands outputs.
Is there a Linux screen like functionality in powershell? I cannot use Windows remote desktop to connect the servers.
You can use Invoke-Command with -InDisconnectedSession, it will start session in asynchronous mode. After you can connect to this session, take data from it, etc. You can read more about this here.
You can create session, disconnect from session, connect back to it.
May be useful for you: New-PSSessionOption with -IdleTimeout.
-IdleTimeout:
Determines how long the session stays open if the remote computer does not receive any communication from the local computer. This includes the heartbeat signal. When the interval expires, the session closes. MSDN Link
I have recently run into double-hop issues with using PSSessions. What I did to work around that is to create a Session Configuration on the remote server that uses the -RunAs parameter to set the credentials that I need the commands on the remote server to be executed as. Then you connect to that session configuration on the remote server, and things should work as expected.
$MyCreds = Get-Credential ''
Invoke-Command -ScriptBlock {
Set-PSSessionConfiguration -Name "My Remote Config" -RunAsCredential $using:MyCreds -Force
} -ComputerName Server01
Then once the session configuration exists I can start a session using that config, and the whole double hop issue is null and void.
Now, mind you I do add some additional security, so that other people cannot use my session config, since that config has my credentials cached on the server (encrypted), and if they used that config they could do whatever they wanted as me. So to accomplish that I get my domain account SID, generate a SDDL line, and restrict access to the Session Config to only my account.
$Searcher = [adsisearcher]"(&(sAMAccountName=$($Creds.UserName.Split('\')[1]))(objectClass=user))"
$Results=$Searcher.FindOne().GetDirectoryEntry()
$MySID = new-object System.Security.Principal.SecurityIdentifier($Results.objectSid.value,0)|% value
$SDDL = "O:NSG:BAD:P(A;;GR;;;BA)(A;;GR;;;IU)(A;;GA;;;$MySID)S:P(AU;FA;GA;;;WD)(AU;SA;GXGW;;;WD)"
$FQDN = $Server.ServerName,$Server.Forest -join '.'
$MySessionName = "DoubleHop-{0}" -f $MyCreds.UserName.Split('\')[1]
Invoke-Command -ScriptBlock {
Register-PSSessionConfiguration -Name $using:MySessionName -RunAsCredential $using:MyCreds -Force -SecurityDescriptorSddl $using:SDDL
} -ComputerName $FQDN -ea 4

How to get return value from remote ps script?

I am using invoke-expression to call a remote script on another server. I was wondering how I can get the status code from that remote script to tell whether the script completes successfully or fails.
In fact, I am not sure whether invoke-expression will work for this return value, if it doesn't , is there any other way to work it around?
Invoke-Expression is not recommended. PSScriptAnalyzer will tell you not to use this if you can avoid this.
The typical way of getting a value from a remote system I'll demonstrate below. The value must be serializable.
$session = New-PSSession # the rest of the command to establish the session
$value = Invoke-Command -session $session -ScriptBlock {
# Do work to get value in say variable $returnValue
return $returnValue
}