Discovery Script is not working - powershell

Below is my script with identifiers scrubbed. The Base Class for Diagnostics is MyCompany.MyApp and the base class of that is Windows Computer. If I run this on the target machine directly with parameters I am getting XML returned. If I run from my PC it is blank. That makes sense locally, but I thought when you run discoveries that the agent runs it on the machine you are targeting? All my other discoveries thus far have been registry so it is possible I am doing something completely wrong.
Param($sourceId, $managedEntityId, $ComputerName)
$api = New-Object -ComObject 'MOM.ScriptAPI'
$discoveryData = $api.CreateDiscoveryData(0, $SourceId, $ManagedEntityId)
$Diagnostics = Invoke-Command -ComputerName $ComputerName {
Get-WebApplication -Name "diagnostics"
}
foreach ($x in $Diagnostics)
{
$instance = $discoveryData.CreateClassInstance("$MPElement[Name='MyCompany.MyApp.Diagnostics']$")
$instance.AddProperty("$MPElement[Name='Windows!Microsoft.Windows.Computer']/PrincipalName$", $ComputerName)
$discoveryData.AddInstance($instance)
}

As the first, most obvious reason, you don't output the discovery data onto output stream (StdOut). Just simple add $discoveryData at a new line by the end of your script.
Moreover, more information about your class and MP architecture required. I'd probably recommend you to use Windows!Microsoft.Windows.ComputerRole as base class and host it on target computer object.
Regards
Max

Related

[System.IO.Path]::GetTempPath() outputs local temp directory when called through Invoke-Command on a remote machine

I'm running PowerShell commands on a remote machine by the use of Invoke-Command -ComputerName. I'm trying to obtain the path of the temporary directory of the remote machine.
Depending on where I call [System.IO.Path]::GetTempPath() it either outputs the expected remote directory C:\Users\…\AppData\Local\Temp or my local temporary directory C:\temp.
This command is not working as expected:
Invoke-Command -ComputerName MyRemoteMachine -ScriptBlock {
Write-Output ([System.IO.Path]::GetTempPath())
}
# Outputs local directory 'C:\temp'
# Expected remote directory 'C:\Users\…\AppData\Local\Temp'
The problem can be reproduced with other commands than Write-Output, e. g. Join-Path.
Contrary, the following code samples all give the expected output of C:\Users\…\AppData\Local\Temp.
Invoke-Command -ComputerName MyRemoteMachine -ScriptBlock {
[System.IO.Path]::GetTempPath()
}
Invoke-Command -ComputerName MyRemoteMachine -ScriptBlock {
$tmp = [System.IO.Path]::GetTempPath(); Write-Output $tmp
}
Invoke-Command -ComputerName MyRemoteMachine -ScriptBlock {
Start-Sleep 1
Write-Output ([System.IO.Path]::GetTempPath())
}
Invoke-Command -ComputerName MyRemoteMachine -ScriptBlock {
Write-Output ([System.IO.Path]::GetTempPath())
Start-Sleep 1
}
Obviously Start-Sleep isn't a solution, but it seems to indicate some kind of timing problem.
Suspecting that the problem isn't limited to GetTempPath() I tried another user-related .NET API, which also unexpectedly outputs my local folder instead of the remote one:
Invoke-Command -ComputerName MyRemoteMachine -ScriptBlock {
Write-Output ([System.Environment]::GetFolderPath([Environment+SpecialFolder]::MyDocuments))
}
How can I use [System.IO.Path]::GetTempPath() and other .NET API in a PowerShell remote session in a predictable way?
Santiago Squarzon has found the relevant bug report:
GitHub issue #14511
The issue equally affects Enter-PSSession.
While a decision was made to fix the problem, that fix hasn't yet been made as of PowerShell 7.3.1 - and given that the legacy PowerShell edition, Windows PowerShell (versions up to v5.1, the latest and final version) will see security-critical fixes only, the fix will likely never be implemented there.
While the linked bug report talks about the behavior originally having been by (questionable) design, the fact that it only surfaces in very narrow circumstances (see below) implies that at the very least that original design intent's implementation was faulty.
The problem seems to be specific to a script block with the following characteristics:
containing a single statement
that is a cmdlet call (possibly with additional pipeline segments)
whose arguments involve .NET method calls, which are then unexpectedly performed on the caller's side.
Workaround:
Make sure that your remotely executing script block contains more than one statement.
A simple way to add a no-op dummy statement is to use $null++:
# This makes [System.IO.Path]::GetTempPath() *locally* report
# 'C:\temp\'
# *Remotely*, the *original* value should be in effect, even when targeting the
# same machine (given that the env. var. modification is process-local).
$env:TMP = 'C:\temp'
Invoke-Command -ComputerName MyRemoteMachine -ScriptBlock {
Write-Output ([System.IO.Path]::GetTempPath()); $null++ # <- dummy statement.
}
Other workarounds are possible too, such as enclosing the cmdlet call in (...) or inserting a dummy variable assignment
(Write-Output ($unused = [System.IO.Path]::GetTempPath()))
Your Start-Sleep workaround happened to work because by definition it too added another statement; but what that statement is doesn't matter, and there's no timing component to the bug.

Powershell DHCP scope options extraction failure using Invoke-Command

I'm trying to extract dhcp scope option info from a list of servers, the list obtained by querying AD for authorized dhcp servers in the domain. I'm using powershell's invoke-command to pass netsh dhcp server \\$servername scope $IP show optionvalue to a remote server. The $IP variable isn't passing the way the command wants to see it. It throws the The command needs a valid Scope IP Address. error.
I'm getting the scope ip address by first running netsh dhcp server \\$servername show scope and extracting the scope ip from that output, storing it in $IP.
I can type the IP in manually into the script and it returns the scope options but passing in the variable always returns the error. I've tested the command itself in a powershell console, both by manually typing in the IP and by creating a variable with the IP (as a string) and using it in the command, which works as well. There are no special characters, that i can tell, or white spaces when i store the IP in the script. I trim those out. I've also tried converting the string to an IP address using [IPAddress], to no avail.
Here is the code that gathers the scope info and then attempts to get the scope options:
foreach ($n in $name) {
$n
$showScopes = Invoke-command -computername $n -ScriptBlock {netsh dhcp server \\$n show scope}
$formatScopeInfo = $showScopes | ? {$_.Trim() -ne "" -and ($_.Contains("Disabled") -or $_.Contains("Active"))}
foreach ($en in $formatScopeInfo) {
$scopeIps = $en.Split("-")
$IP = [IPAddress]$scopeIps[0].Trim()
$IP.IPAddressToString
Invoke-Command -ComputerName $n -ScriptBlock {netsh dhcp server \\$n scope $IP.IPAddressToString show optionvalue}
}
The first foreach works and removes the lines that don't contain scope info. The second foreach partially works, it does strip out the IP. Initially i just stored it as a string, $IP = $scopeIps[0].Trim() but that wasn't working. I tried a number of things. I tried converting the octets to integers and joining them with ".", I tried to store the whole command as as a string and pass that into the Invoke-Command. Like so:
$command = "netsh dhcp server \\$n scope $IP show optionvalue"
Invoke-Command -ComputerName $n -ScriptBlock {$command}
The ultimate goal is to be able to extract any configured scope options, wherever they may be configured (server, reservation...etc). I fear I've gotten to that point where I'm so hyper-focused on what I think is the problem, that I may be missing something simple and/or crucial elsewhere. My opinion is that the command wants to see an actual IP address but my attempts to pass the variable that way have failed (and it works in the powershell console when saved as a string).
Fair disclosure, I'm still very much a novice and I was reluctant to post my code. I see so many on here with incredibly elegant solutions to things and, by comparison, my stuff seems extremely clunky. I've never had to post before as most times I can find/figure out where i've gone wrong. But I endeavor to learn and I have spent the better part of this weekend googling with no results. I have seen the script out there that works for pre 2012 servers but I really enjoy writing my own. I'm not looking to have anyone "do it for me", if you can point me down the appropriate rabbit hole; I'm happy to venture down it. Any suggestions on the code itself (appearance, better way of doing something...etc) are appreciated as well.
Apologies for the verbosity. I'm stuck and appreciate any help.
In your invoke-command you are not passing parameter,it should be like this:
$showScopes = Invoke-command -computername $n -ScriptBlock {
param($n)
netsh dhcp server \\$n show scope
} -argumentlist $n
and
Invoke-Command -ComputerName $n -ScriptBlock {
param($n,$IP)
netsh dhcp server \\$n scope $IP.IPAddressToString show optionvalue
} -argumentlist $n,$IP

Powershell run script with Import-Module

I have created a module for my admin group with some functions that automate some procedures we commonly perform (add administrators to remote machines, C drive cleanup, etc...)
One of the prerequisites for these functions is the generation of a series of 7 credentials, one for each domain we work in.
Is there a way to get a scriptblock to run when you import a module, or is this something I should add to each persons profile?
A commenter mentioned I could just add it to the module.psm1 file, but that didn't work. Here is the code I am trying to run.
$creds = Import-Csv [csvfile]
$key = Get-Content [keyfile]
foreach ($cred in $creds) {
$user = $cred.User
$password = $cred.Hash | ConvertTo-SecureString -Key $key
$i = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $user,$password
New-Variable -Name ($cred.Domain + "_Cred") -Value $i -Force
}
Running this manually works fine, but it isn't creating the credentials when run from the Import-Module command.
Any code that's not a function will run when you import the module.
A handy tip when working with modules: & and . have what may be undocumented functionality. With either you can give two arguments, the first is a module reference (from get-module or similar) and second is a script. With the module reference parameter the script will run in the context of the module. So for example:
& $myMod {$usa_cred}
will output the value of $use_cred even if it hasn't been exported. This is useful for debugging scripts. Also modules can have embedded modules and & $myMod {gmo} will list those sub modules. By nesting & or . you can access sub-modules context.

Powershell delete MSMQ remotely

I was wondering if it was possible to delete queues remotely via PowerShell? I have the following script:
cls
[Reflection.Assembly]::LoadWithPartialName("System.Messaging")
$computers = #("comp1","comp2","comp3");
foreach($computer in $computers) {
$messageQueues = [System.Messaging.MessageQueue]::GetPrivateQueuesByMachine($computer);
foreach ($queue in $messageQueues) {
$endpoint = [string]::Format("FormatName:DIRECT=OS:{0}\{1}", $computer, $queue.QueueName);
Write-Host $endpoint
[System.Messaging.MessageQueue]::Delete($endpoint);
}
}
This works fine, if I was running it on the machine whose queues I want to delete however when I run this remotely I get the error:
The specified format name does not support the requested operation. For example, a direct queue format name cannot be deleted.
Any ideas if this can be done?
EDIT
Oddly, I have figured I can remote onto the machine via PowerShell and execute a script block. However, I don't understand the difference between doing:
THIS:
$endpoint = [string]::Format("FormatName:DIRECT=OS:{0}\{1}", $computer, $queue.QueueName);
Invoke-Command -ComputerName $computer -ScriptBlock { [Reflection.Assembly]::LoadWithPartialName("System.Messaging"); [System.Messaging.MessageQueue]::Delete($endpoint) };
AND THIS:
Invoke-Command -ComputerName $computer -ScriptBlock { [Reflection.Assembly]::LoadWithPartialName("System.Messaging"); [System.Messaging.MessageQueue]::Delete("FormatName:DIRECT=OS:MY_SERVER\some.endpoint") };
The value of $endpoint is the same however, for some odd reason it doesn't like the variable approach though both values are identical. I tested this by setting $endpoint then calling delete. I get the error:
Exception calling "Delete" with "1" argument(s): "Invalid value for parameter path."
What I'm trying to say is if I hard code the value as part of the argument it works but assign it to a variable then invoke the method I get an error
For historic purposes if anyone else is experiencing this issue or is wondering how to delete queues remotely then please see below.
How do I delete private queues on a remote computer? It is possible to delete queues remotely. This can be achieved using the command Enable-PSRemoting -Force. Without this, you encounter the issue #JohnBreakWell indicated (see his link to MSDN).
The scope of variables when using Invoke-Command? The problem I found was the variables I declared were simply out of scope (script block was unable to see it). To rectify this, I simply did the following:
The important bit being the argument list and the use of param.
$computers = #("comp1","comp2");
foreach($computer in $computers) {
[Reflection.Assembly]::LoadWithPartialName("System.Messaging");
$messageQueues = [System.Messaging.MessageQueue]::GetPrivateQueuesByMachine($computer);
foreach ($queue in $messageQueues) {
$endpoint = [string]::Format("FormatName:DIRECT=OS:{0}\{1}", $computer, $queue.QueueName);
Enable-PSRemoting -Force
Invoke-Command -ComputerName $computer -ScriptBlock {
param ($computer, $endpoint)
[Reflection.Assembly]::LoadWithPartialName("System.Messaging");
[System.Messaging.MessageQueue]::Delete($endpoint)
}
} -ArgumentList $computer, $endpoint
}
You cannot delete a remote private queue.
You need to perform the operation locally to the queue.
From MQDeleteQueue:
Remarks
(2nd paragraph)
"Private queues registered on a remote computer ... cannot be deleted."
As Dr. Schizo mentioned, you'll need to execute
Enable-PSRemoting -Force
on the remote machine, but then, assuming you're using Server 2012 r2, it's as simple as:
Invoke-Command -ComputerName COMPUTERNAME { Get-MsmqQueue -Name QUEUENAME | Remove-MsmqQueue }

How to create a com object in another domain?

I'm trying to use PowerShell to quickly find the Scheduled Tasks in the root folder of a remote server. I find all sorts of scripts that others have written, but they're either looking at the localhost or on a server in the same domain. I support servers in dozens of domains, so I need some way to pass along credentials.
Here's the meat of my script:
$server = "<computername>"
$schedule = new-object -com("Schedule.Service")
$Schedule.connect($server)
$folder = $schedule.GetFolder("")
$tasks = $folder.GetTasks("")
foreach($task in $tasks) {
if (($task = $Folder.GetTasks(0))) {
$Tasks| ForEach-Object {[array]$results += $_}
$Tasks | Foreach-Object {
New-Object -TypeName PSCustomObject -Property #{
'Name' = $_.name
<etc.>
<etc.>
}
}
}
That code works fine either on my localhost or a server in the same domain as my workstation. In other scripts, I use Get-Credential to create $creds and (in various ways) pass that to the appropriate cmdlet. But with this one, I'm not sure. 'New-Object' doesn't accept a -Credential parameter. I've tried wrapping various parts inside an Invoke-Command scriptblock, since that accepts -Credential, but it fails in various ways. I'm not sure what needs to be wrapped in Invoke-Command--just the new-object? The foreach loop? The entire thing?
Thanks in advance.
When doing the Connect call, you can pass the server, domain, username, and password:
$Schedule.Connect($serverName, $user, $domain, $password);
This should allow you to use that object on the new domain.
MSDN Reference