Variable stuck in a "for" powershell - powershell

I am have been trying to get out, the variable $groupUsers (array) that is stuck in inside a for.
I have being researching about the scopes of the variables an I have found that a variable need to be global if I want it to be available in all the function.
I have tried putting the array outside the foreach, the if the for and even converting it to $global:groupUsers but it doesn't work.
function get-localadmins{
[cmdletbinding()]
param( [string[]] $machineslist)
#split computers list
$separator = ","
$option = [System.StringSplitOptions]::RemoveEmptyEntries
$computerSplit = $machineslist.Split($separator, 20000 ,$option)
#input credentials
$credential = Get-Credential kimo\admin0987
######## Create empty array to use in the for #####
$groupUsers = #()
foreach ($computer in $computerSplit)
{
$session = new-pssession -computername $computer -Credential $credential
If ($? -eq $false )
{
$alert = "La conexion remota de ""PSSRemoting""no pude ser establecida con este equipo porfavor verifica WinRM has been updated to receive requests. WinRM service type changed successfully. WinRM service started. WinRM has been updated for remote management. WinRM firewall exception enabled. que la politica de este activada o corre el commando asdfjsfda para habilitar powershell remoto "
}
else
{
new-pssession -computername $computer -Credential $credential
$admingroup2 = Invoke-Command -ComputerName $computer -Credential $credential -ScriptBlock {
$group = get-wmiobject win32_group -ComputerName localhost -Filter "LocalAccount=True AND SID='S-1-5-32-544'"
$query = "GroupComponent = `"Win32_Group.Domain='$($group.domain)'`,Name='$($group.name)'`""
$list = Get-WmiObject win32_groupuser -ComputerName localhost -Filter $query
$admingroup = $list | select PartComponent # | % {$_.substring($_.lastindexof("Domain=") + 7).replace("`",Name=`"","\")}
$clean = $admingroup
for($i = 0; $i -lt $clean.length; $i += 1)
{
$string = $clean[$i] | Out-String
$domainPosition = $string.lastindexof('Domain=') + 7
$nextComma = $string.substring($domainPosition).indexOf(',')
$domain = $string.substring($domainPosition,$nextComma-1)
$namePosition = $string.lastindexof('Name="') + 6
$name = $string.substring($namePosition)
#####this is the variable that I can get outside the for ######
$groupUsers += $domain + "\" + $name
Write-Host $groupUsers "inside for"
}
Write-Host $groupUsers "outside for"
}
}
}
############### I need to get the variable here ###################
$groupUsers| ConvertTo-Html
Write-Host $groupUsers "out side the for-each "
}
$222 = get-localadmins localhost,machine1,machine2

It seems like the only problem you have here is that you aren't getting $groupUsers returned from the function. That's because you're never returning it!
Write-Host writes directly to the host application as a way of showing data without including it in the pipeline.
To return data, use Write-Object or just don't qualify it at all:
$groupUsers += $domain + "\" + $name
Write-Host $groupUsers "inside for"
}
Write-Host $groupUsers "outside for"
$groupUsers # this will return it
}
Then your $222 variable will contain the value.
You don't need $Global scope here.

Related

Powershell return variable from within a Invoke-Command

I'm developing a powershell script (and kind of new to it) to go to a couple of servers and extract the RDP logons, so we can check if a certain policy is being followed.
So I've search a bit and now I got the script output exatcly as I want. But now I want to send the result over email.
But I have a problem, because the variable which is output to console (with the info I need) is inside a Invoke-Command, so I cannot use it after outside the Invoke-Command to send the email.
This is my code:
$ServersToCheck = Get-Content "C:\Temp\Servers-RDP2.txt"
foreach ($server in $ServersToCheck) {
Invoke-Command -ComputerName $server -ErrorAction SilentlyContinue {
$username = "user"
$FilterPath = "<QueryList><Query Id='0'><Select Path='Microsoft-Windows-TerminalServices-RemoteConnectionManager/Operational'>*[System[(EventID=1149) and TimeCreated[timediff(#SystemTime) <= 604800000]]] and *[UserData[EventXML[#xmlns='Event_NS'][Param1='{0}']]]</Select></Query></QueryList>" -f $username
$RDPAuths = Get-WinEvent -ErrorAction SilentlyContinue -LogName 'Microsoft-Windows-TerminalServices-RemoteConnectionManager/Operational' -FilterXPath $FilterPath
[xml[]]$xml = $RDPAuths | Foreach { $_.ToXml() }
$EventData = Foreach ($event in $xml.Event) {
New-Object PSObject -Property #{
TimeCreated = (Get-Date ($event.System.TimeCreated.SystemTime) -Format 'dd-MM-yyyy hh:mm:ss')
User = $event.UserData.EventXML.Param1
Domain = $event.UserData.EventXML.Param2
Client = $event.UserData.EventXML.Param3
Server = hostname
}
}
$EventData | FT
}
}
So, I need to use $EventData outside the Invoke-Command so I can add the results of all servers and then send it over by email.
How can I use that variable outside the Invoke-Command?
Thanks

How to pass variable value from a map defined outside invoke command and to be used after invoke command

I am writing a script in powershell where after login with User 1 on a system, it will switch to user 2 and then make a connection to database with this user. However, the dbinstance details, port No and Computer name to be passed in invoke command will be defined as a map before the 2nd invoke command i.e. when it will invoke the command to open powershell with 2nd user(db user). It is able to take userid in this case i.e. when to invoke the powershell connection with 2nd user, however it is not able to pass the values of dbinstance and port to next sqlcmd invoke. Below is the code for reference. In this code it works fine while getting $inputMap.UserNameP, however it fails in passing $inputMap.DBInstance,$inputMap.PortNo.
$UserName = 'User1'
$securekey = #'
securekey1
'# |ConvertTo-SecureString -AsPlainText -Force;
$concreds=New-Object System.Management.Automation.PSCredential -ArgumentList $UserName, $securekey;
Invoke-Command -Credential $concreds -ComputerName 'abc.domainname'-Authentication Credssp -ScriptBlock {
function checkFaultHighUtilization() {
$local:ExecStdOperatorOut=Invoke-Command -ScriptBlock {
$inputMap=#{"UserNameP"="User2";"DBInstance"="databaseinstancename";"PortNo"="portnumber";};
$securekey1 = "securekey1"
$finalresult = #()
$securekey2 = $securekey1 | ConvertTo-SecureString -AsPlainText -Force;
$concreds=New-Object System.Management.Automation.PSCredential -ArgumentList $inputMap.UserNameP, $securekey2;
Invoke-Command -Credential $concreds -ComputerName 'computername' -Authentication Credssp -ScriptBlock {
$var1=Invoke-Sqlcmd -query "
Begin
select * from db
End" -ServerInstance "$inputMap.DBInstance,$inputMap.PortNo"
##if (($var1.count) -gt 0) {
foreach($row in $var1){
$finalresult+=$row.a+':'+$row.b+':'+$row.c
echo $finalresult
}
}
}
$local:ExecStdOperatorRet=if($local:ExecStdOperatorOut) {0} else {1}
return $local:ExecStdOperatorRet,$local:ExecStdOperatorOut;
};
$ESExecReturn,$ESExecOutput=checkFaultHighUtilization
$ESExecOutput=($ESExecOutput | Out-String).Trim();
Write-output "ESExecOutput:";
Write-output $ESExecOutput;
Write-output ":ESExecOutput";Write-output $("ESExecError:" + $Error + ":ESExecError");
Write-output $("ESExecReturn:" + $ESExecReturn + ":ESExecReturn");
}
$scriptBlockOne = {
$variableA = "Hello World"
return $variableA
}
$scriptBlockTwo = {
param (
$inputString
)
Write-host $inputString
}
$invokeCommandReturn = Invoke-Command -ScriptBlock $scriptBlockOne
Invoke-Command -ScriptBlock $scriptBlockTwo -ArgumentList $invokeCommandReturn
You're trying to use expressions such as $inputMap.DBInstance as-is inside an expandable string ("..."), which is syntactically not supported.
To use expressions, you must enclose them in $(...), the subexpression operator.
See this answer for a comprehensive discussion of string interpolation in PowerShell.
Therefore:
# ...
$var1 = Invoke-Sqlcmd -Query "
Begin
select * from db
End" -ServerInstance "$($inputMap.DBInstance),$($inputMap.PortNo)" # Note the $()
# ...

Powershell lookup RAM information on remote computer and save Partnumbers in to diffent variable

I have a simple script that can pull RAM partnumbers from a remote computer and search google for it. But it does not work as intended. If there's only 1 RAM module installed in the remote computer, it works great google opens with the search result for the Partnumber, yay!.
if there are more than 1 RAM module installed in the remote computer, the first Partnumber in the variable gets searched for in Google. The 2'nd, 3'rd, 4'th partnumber gets typed in to Chrome tab 2,3,4 as an address.
How can I get Chrome to search for all Partnumbers via Google?
My script:
$ComputerName = Read-Host "Write Computer Name"
Get-WmiObject Win32_PhysicalMemory -computername $ComputerName
$ToChrome = Read-Host 'Do you want to search Google for the Partnumber(s)? Y Or N'
if ($ToChrome -eq 'Y') {$Partnumber = Get-WmiObject Win32_PhysicalMemory -computername $ComputerName | select -expandproperty Partnumber
Start-Process "chrome.exe" ("https://www.google.com/search?q=$Partnumber")}
if ($ToChrome -eq 'n') {Continue}
That is because chrome.exe interprets the space between the part numbers as new addresses.
I took the liberty to pimp the script with try&catch,a logfile output and the computername as a parameter so that you can call it as Get-MemoryPropertyAndSearchWithGoogle.ps1 -ComputerName ComputerName1
For my testing I used the attribute DeviceLocator as my PartNumber was empty.
#Get-MemoryPropertyAndSearchWithGoogle.ps1
Param (
[Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
[string]$ComputerName
)
$ErrorPreference='Stop'
$ErrorActionPreference='Stop'
$LogFilePath = "C:\Temp\$((Get-Date).ToString("yyyy-MM-dd"))$($ComputerName)Get-MemoryPropertyAndSearchWithGoogle.log"
[string]$LogFileString = ""
#$Property = "PartNumber"
$Property = "DeviceLocator"
$ErrorExists = $false
$ComputerMemoryObjects = #()
try
{
$ComputerMemoryObjects = Get-WmiObject Win32_PhysicalMemory -ComputerName $ComputerName -Property *
$LogFileString += "$((Get-Date).ToString("yyyy-MM-dd_HH:mm:ss"))#INF#Get-WmiObject Win32_PhysicalMemory -ComputerName $($ComputerName)`n"
}
catch
{
$LogFileString += "$((Get-Date).ToString("yyyy-MM-dd_HH:mm:ss"))#ERR#$($error[0].exception.message)`n"
$ErrorExists = $true
}
[string]$SearchString = ""
foreach ($SingleComputerMemoryObject in $ComputerMemoryObjects)
{
if ($SearchString)
{
$SearchString += "+OR+"
}
$SearchString += "$($SingleComputerMemoryObject.$Property)"
}
$ToChrome = Read-Host 'Do you want to search Google for the Partnumber(s)? Y Or N'
if ($ToChrome -eq 'Y')
{
if ($SearchString)
{
try
{
Start-Process "chrome.exe" ("https://www.google.com/search?q=$($SearchString)")
$LogFileString += "$((Get-Date).ToString("yyyy-MM-dd_HH:mm:ss"))#INF#chrome.exe started with searchstring:`"$($SearchString)`"`n"
}
catch
{
$LogFileString += "$((Get-Date).ToString("yyyy-MM-dd_HH:mm:ss"))#ERR#$($error[0].exception.message)`n"
$ErrorExists = $true
}
}
else
{
$LogFileString += "$((Get-Date).ToString("yyyy-MM-dd_HH:mm:ss"))#INF#`$SearchString is empty`n"
}
}
if (!($ErrorExists))
{
$LogFileString += "$((Get-Date).ToString("yyyy-MM-dd_HH:mm:ss"))#INF#ScriptCompletedWithoutErrors`n"
}
$LogFileString | Out-File $LogFilePath
$LogFileString
You get multiple objects from Get-WmiObject. You need a loop if you want to do something for each of them.
Also, URL-encoding things that you put into a URL is a good idea. and maybe putting it in double-quotes won't hurt.
Add-Type -AssemblyName System.Web # for [System.Web.HttpUtility]::UrlEncode()
$ComputerName = Read-Host "Write Computer Name"
$installed_memory = Get-WmiObject Win32_PhysicalMemory -ComputerName $ComputerName | Select-Object Manufacturer,PartNumber,SerialNumber,DeviceLocator,Capacity
$installed_memory | Format-Table -AutoSize
$ToChrome = Read-Host 'Do you want to search Google for the Partnumber(s)? Y Or N'
if ($ToChrome -eq 'Y') {
$unique_numbers = $installed_memory.Partnumber.Trim() | Sort-Object -Unique
foreach ($number in $unique_numbers) {
$query = [System.Web.HttpUtility]::UrlEncode('"' + $number + '"')
Start-Process chrome.exe "https://www.google.com/search?q=$query"
}
}
Powershell has a handy convenience feature: When you have an array of objects, you can query nested properties from all of them in one go.
For example, if there are 4 Win32_PhysicalMemory objects in $installed_memory, then
$installed_memory.Partnumber.Trim()
gives you 4 readily trimmed part numbers in a single step.

Unable to create printers on Windows 2008 using WMI Class

When using the below code to create printer on Windows 2008 servers to create the printers
`function CreatePrinterPort {
$server = $args[0]
$port = ([WMICLASS]“\\$server\ROOT\cimv2:Win32_TCPIPPrinterPort").createInstance()
$port.Name= $args[1]
$port.SNMPEnabled=$false
$port.Protocol=2
$port.HostAddress= $args[2]
$port.Put()
}
function CreatePrinter {
$server = $args[0]
$print = ([WMICLASS]“\\$server\ROOT\cimv2:Win32_Printer”).createInstance()
$print.Drivername = $args[1]
$print.PortName = $args[2]
$print.Shared = $true
$print.Published = $false
$print.Sharename = $args[3]
$print.Location = $args[4]
$print.Comment = $args[5]
$print.DeviceID=$args[2]
$print.Put()
}
$printers = Import-Csv “C:\printers.csv”
foreach ($printer in $printers) {
CreatePrinterPort $printer.Printserver $printer.Portname $printer.IPAddress
CreatePrinter $printer.Printserver $printer.Driver $printer.Portname $printer.Sharename $printer.Location $printer.Comment $printer.Printername
}'
I am getting the following error. The port creation function is working.
"IsSingleton : False
Exception calling "Put" with "0" argument(s): "Generic failure "
At line:23 char:1
+ $print.Put()
+ ~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : DotNetMethodException"
I have importing all the details from a CSV file and it contains all the information.
Any suggestions?
I'm pretty sure you're both doing this the hard way, and didn't read the MSDN page for the Win32_Printer class. It says in the remarks that you have to enable the SeDriverUpdate privilege before you can issue the Put method, so I have a feeling that's where you're getting your error from.
Next, use the Set-WMIInstance cmdlet, or the newer 'New-CIMInstance` if you can. Calling the classes directly is possible I'm sure, but if the server is local it won't enable the privileges that you need to create a printer.
Lastly, you could make your function better if you made it an advanced function, and allowed piped data. Check this out:
function CreatePrinter {
[cmdletbinding()]
Param(
[Parameter(ValueFromPipelineByPropertyName=$true)]
[Alias('Printserver')]
$Server,
[Parameter(ValueFromPipelineByPropertyName=$true)]
[Alias('Driver')]
$Drivername,
[Parameter(ValueFromPipelineByPropertyName=$true)]
$PortName,
[Parameter(ValueFromPipelineByPropertyName=$true)]
$Sharename,
[Parameter(ValueFromPipelineByPropertyName=$true)]
$Location,
[Parameter(ValueFromPipelineByPropertyName=$true)]
$Comment,
[Parameter(ValueFromPipelineByPropertyName=$true)]
[Alias('IPAddress')]
$HostAddress,
[Parameter(ValueFromPipelineByPropertyName=$true)]
[Alias('PrinterName')]
$Name
)
PROCESS{
$PortArgs = #{
Name = $PortName
SNMPEnabled = $false
Protocol = 2
HostAddress = $HostAddress
}
Set-WmiInstance -Class Win32_TCPIPPrinterPort -Arguments $PortArgs -ComputerName $Server -PutType UpdateOrCreate -EnableAllPrivileges
$PrinterArgs = #{
Drivername = $Drivername
PortName = $PortName
Shared = $true
Published = $false
Sharename = $Sharename
Location = $Location
Comment = $Comment
DeviceID = $PortName
Name = $Name
}
Set-WmiInstance -Class Win32_Printer -Arguments $CmdArgs -ComputerName $Server -PutType UpdateOrCreate -EnableAllPrivileges
}
}
That creates the port, and then the printer. I suppose you could split it into two functions, but do you really see needing one without the other? Plus you can pipe your CSV data directly into it like this:
Import-CSV "C:\printers.csv" | CreatePrinter
That's it, that will create ports and printers for all records in the CSV. Plus I used the UpdateOrCreate enum, so if something isn't right you could correct the CSV and just run it again to refresh the settings to the correct settings without having to worry about deleting old copies and making new copies of things.

Powershell Timeout After two Seconds

I'm new to powershell. I read some lines on www.powershell.com. Now I need your help to solve a problem. I want to read the UUID from clients in the Network. Therefore I created a document "pcs.txt" where all PCs are stored.
$pc = Get-Content pcs.txt #Read content of file
$cred = Get-Credential “domain\user”
for ($i=0; $i -lt $pc.length; $i++) {
$Result=test-connection -ComputerName $pc[$i] -Count 1 -Quiet
If ($Result -eq 'True')
{
$uuid = (Get-WmiObject Win32_ComputerSystemProduct -ComputerName $pc[$i] -Credential $cred).UUID
$Ausgabe=$pc[$i] + ';'+$uuid
$Ausgabe
}
else
{
$Ausgabe=$pc[$i] + '; UUID nicht erhalten'
$Ausgabe
}
}
First I test if the ping works. When the ping works I try to get the uuid.
Sometimes I don't get the uuid even if the ping worked. So I would like to code a timeout, which say -> go to next pc when you don't have the uuid after 2 seconds.
Can you help me please?
Alas, there is no timeout parameter for Get-WmiObject commandlet. There is a feature request in MS Connect, but it is from 2011 and still open.
A workaround, which I haven't tested is available by using System.Management. I'll copy-and-paste it here in case the link goes dead. (And I hate SO answers that only contain links to resouces that may or may not exist...)
Function Get-WmiCustom([string]$computername,[string]$namespace,[string]$class,[int]$timeout=15){
$ConnectionOptions = new-object System.Management.ConnectionOptions
$EnumerationOptions = new-object System.Management.EnumerationOptions
$timeoutseconds = new-timespan -seconds $timeout
$EnumerationOptions.set_timeout($timeoutseconds)
$assembledpath = "\\" + $computername + "\" + $namespace
#write-host $assembledpath -foregroundcolor yellow
$Scope = new-object System.Management.ManagementScope $assembledpath, $ConnectionOptions
$Scope.Connect()
$querystring = "SELECT * FROM " + $class
#write-host $querystring
$query = new-object System.Management.ObjectQuery $querystring
$searcher = new-object System.Management.ManagementObjectSearcher
$searcher.set_options($EnumerationOptions)
$searcher.Query = $querystring
$searcher.Scope = $Scope
trap { $_ } $result = $searcher.get()
return $result
}
I found a good workaround!
http://theolddogscriptingblog.wordpress.com/2012/05/11/wmi-hangs-and-how-to-avoid-them/
Here my working code:
$pc = Get-Content pcs.txt #FILE FROM THE HARDDISK
$cred = Get-Credential “DOMAIN\USER” #
for ($i=0; $i -lt $pc.length; $i++)
{
$Result=test-connection -ComputerName $pc[$i] -Count 1 -Quiet
If ($Result -eq 'True')
{
$WMIJob = Get-WmiObject Win32_ComputerSystemProduct -ComputerName $pc[$i] -Credential $cred -AsJob
$Timeout=Wait-Job -ID $WMIJob.ID -Timeout 1 # the Job times out after 1 seconds.
$uuid = Receive-Job $WMIJob.ID
if ($uuid -ne $null)
{
$Wert =$uuid.UUID
$Ausgabe=$pc[$i] + ';'+$Wert
$Ausgabe
}
else
{
<#$b = $error | select Exception
$E = $b -split (:)
$x = $E[1]
$Error.Clear() #>
$Ausgabe=$pc[$i] + '; got no uuid'
$Ausgabe
}
}
else
{
$Ausgabe='PC not reached through ping.'
$Ausgabe
}
}
I hope I can help somebody with that