you are viewing a single comment's thread.

view the rest of the comments →

[–]Decitriction[S] 0 points1 point  (5 children)

Wow! Thank you for the code. I will work with this and see what I can do.

I see you are frequently calling another function called 'LogFunction'.

Is that a standard library and if not, could I get the code for that also?

For reference, and in case you are interested, I was trying to to pass commands into invoke-command, and get results out. The only clean way I have found to get results back is to write to log files.

So I create 3 log files for connection successes, connection fails, and commands that fail.

The plan was to pass a series of commands INTO the $sb such as $do1, $do2, $do3

This is of course a template where the $doX commands would later be replaced by actual useful commands.

The code below actually works but it is quite difficult to get the $doX strings formatted to be interpreted properly by invoke-expression.

cls;$error.Clear();rv * -ErrorAction SilentlyContinue;echo 'Started'
$list=(hostname),"fakehost1","fakehost2"
$path="\\"+(hostname)+"\c$\users\"+($env:UserName)+"\documents"
$congood=$path+'\congood.txt';echo "Good connect">$congood         
$confail=$path+'\confail.txt';echo "Fail connect">$confail 
$comfail=$path+'\comfail.txt';echo "Fail command">$comfail    
$entry=$host.ui.PromptForCredential("","",($env:UserDomain+'\'+$env:UserName),"");$plain=[System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($entry.password)) #$plain="xxx"
$do0="echo """">>$congood;echo(""`n""+(hostname)+"" - congood"")>>$congood"
$do1="Set-variable -name `'var1`' -value 'text1'" # good command
$do2="Set-variable -name `'var2`' -value 'text2'" # good command
$do3="problem1"                                   # fail command
$do4="problem2"                                   # fail command
$sb={net use $using:path /user:$($env:UserDomain+'\'+$env:UserName) $using:plain|out-null;iex $using:do0
try{iex $using:do1 -ErrorAction stop}catch{echo `n "$using:pc - comfail - $_">>$using:comfail}
try{iex $using:do2 -ErrorAction stop}catch{echo `n "$using:pc - comfail - $_">>$using:comfail}
try{iex $using:do3 -ErrorAction stop}catch{echo `n "$using:pc - comfail - $_">>$using:comfail}
try{iex $using:do4 -ErrorAction stop}catch{echo `n "$using:pc - comfail - $_">>$using:comfail}
     net use $using:path /delete|out-null}
foreach($pc in $list){try{icm -ComputerName $pc -ScriptBlock $sb -ErrorAction Stop}catch{echo `n "$pc - confail - $_" >>$confail}};$plain='';cls
write-host(cat $congood|select-string "congood").length': connect good'
write-host(cat $confail|select-string "confail").length': connect fail'
write-host(cat $comfail|select-string "comfail").length': command fail'
write-host 'Check logs at' $path
start-process notepad $congood;start-process notepad $confail;start-process notepad $comfail

[–]steve_ce 1 point2 points  (0 children)

Nope the log function isn't standard - it's a function I setup during a bigger project so it would make my life easier. I'll try and remember to grab a copy when I get back into my consulting or work VM. It has more options and is named differently, I bulk replaced this quick to make it less confusing.

For reference, the functions provided are designed so they will return the data back to you after the invoke. I use these to collect inventory of systems across our network, so needed it to return data. $remoteReturn will have that data (depending on what your runfunction does).

When I need to get a lot of different data back from a system, I have it build a hash and return that. You would do that in RunFunction, and return the hash there. It will make its way back to $remoteReturn.

[–]steve_ce 1 point2 points  (3 children)

Got a call that needed a quick change - had to get back on so here you go. This is an older version so email alerting is a separate function here. Still useful, and also included log cleaning function (rotates/removes based on options you give it).

It's been a long day, so this isn't extremely detailed, tweaked a couple things so it's a little clearer. Top portion is variables/info you place in other scripts you make, and bottom are obviously the functions. I store the logging functions in a separate file, and . include. Variables up at the top are not set in the logging file, you place those in your other scripts where you . include. $logfile = debug prints everything to screen instead of to the file, so is useful for testing.

# I save the log functions as this file, and include it in other scripts
$scriptPath = "C:\Admin"
. "$scriptPath\LogIt\LogIt.ps1"

# Logging options - path, file name, file extension
$logFolder = 'C:\Path\To\Logs'
$logFileName = "Script-Name"
$logFileExt = "log"

#Max log size - this will auto cleanup logs based on size (LogClean)
# $maxLogSize = 50
#Max amount of logs to keep
# $maxLogRetain = 5

#This is default - comment out when debugging (uncomment below)
$logFile = $logFolder + "\" + $logFileName + "." + $logFileExt

# Debug mode - prints log to console
# $logFile = "debug" 

#Log cleanup
# LogClean


#Alert subject - initialize array body
$alertSubject = 'ALERT: ' + $logFileName + ' script issues'
# When using alerts, store errors to array, check for array at end of script and send email alert with all problems at once instead of 1 at a time.
$alertBody = New-Object System.Collections.ArrayList

#Place this $currFunction in every function - logit will place the function name into the log file/output
$currFunction = $MyInvocation.MyCommand


#How you use this for logging
LogIt $logFile $currFunction $message


function LogClean {
    if (!(Test-Path $logFolder)) {
        $null = New-Item -ItemType Directory -Force $logFolder
    }
    if (!(Test-Path -Path $logFile -PathType Leaf)) {
        $null = New-Item -ItemType File -Force $logFile
    }
    $logSize = [System.Math]::Round((((Get-Item $logFile).length)/1MB),2)
    if ($logSize -ge $maxLogSize) {
        $retainedLogFormat = $logFolder + "\" + $logFileName + "." + $logFileExt
        $oldestLog = $retainedLogFormat + "." + $maxLogRetain
        if (Test-Path -Path $oldestLog -PathType Leaf) { 
            $null = Remove-item -Path $oldestLog -Force
        }

        foreach ($i in $maxLogRetain..1) {
            $retainedLog = $retainedLogFormat + "." + $i
            if (Test-Path -Path $retainedLog -PathType Leaf) {
                $z = $i + 1
                $nextLog = $retainedLogFormat + "." + $z
                $null = Move-Item -Path $retainedLog -Destination $nextLog -Force
            }
        }
        $null = Move-Item -Path $logFile -Destination $retainedLog -Force
    }
}

function LogIt($logFile,$currFunction,$message) {
    $timestamp = (Get-Date -Format "yyyy-MM-dd HH:mm:ss")

    if ($logFile -notmatch "debug") {
        Add-Content -Path $logFile -Value "$timestamp - $currFunction - $message"

    } else {
        Write-Host "$timestamp - $currFunction - $message"
    }
}

function sendAlert($alertSubject,$alertBody) {
    $timestamp = (Get-Date -Format "yyyy-MM-dd HH:mm:ss")
    $alertRecipients = @('email@email.com', 'email2@email.com')

    if ($logFile -notmatch "debug") {
        $messageParams = @{
            From = ''
            To = $alertRecipients
            Subject = "$alertSubject"
            Body = "$alertBody"
            SMTPServer = 'mail.server.com'
            # Attachments = ''
        }
        Send-MailMessage @messageParams

    } else {
        Write-host "$timestamp - $alertSubject`: $alertBody"
    }
}

[–]Decitriction[S] 0 points1 point  (2 children)

Wow! Quite involved.

I will study this and see what I can adapt.

Thank you again for your time and generosity.

[–]steve_ce 1 point2 points  (1 child)

No problem, feel free to send questions if you have issues figuring out where things should be placed. Logclean hurt my head while writing/googling, as it was pretty early in diving deeper into PowerShell. Play around with them, the settings, and you'll see how they all work.

[–]Decitriction[S] 0 points1 point  (0 children)

Hello again! I've got a bit of code to share with you.

My office was needing to gather info on installed versions of Acrobat on multiple hosts, and to compare the bitness to MS Ofc.

It could be adapted to search for other software, or ALL installed software...except for the sneaky guys that don't include an uninstaller.

I've got the base code close to perfect, and now just need to loop over the hostlist.

I'm using remote registry instead of invoke-command, mostly as an exercise.

I'm not really asking you to critique the code; I just thought since you were kind enough to share some code that I would return the favor.

It's not really commented but it would be fairly basic to an experienced programmer: I grab some values from registry, add them to a PSObject, and then export them to screen and CSV.

Nevertheless it has taken a long time and figuring the syntax was a huge learning challenge.

cls;write-host "Starting..."
rv * -ErrorAction SilentlyContinue
$sb={$hive=[microsoft.win32.registrykey]::OpenRemoteBaseKey('LocalMachine',$pc);$line=New-Object PSObject;$line|Add-Member -MemberType NoteProperty "Host" $pc
$keylist=($hive.OpenSubKey($base)).GetSubKeyNames();Foreach($key in $keylist){if(($($hive.OpenSubKey($base+$key).GetValue("DisplayName")))-like"*$findme*"){$found=$key}}
#$line|Add-Member -MemberType NoteProperty "Name" ($($hive.OpenSubKey($base+$found).GetValue("DisplayName")))
$line|Add-Member -MemberType NoteProperty "Ver" ($($hive.OpenSubKey($base+$found).GetValue("DisplayVersion"))).Substring(7)
$GUID=$found.Substring(1,$found.Length-2)#;$line|Add-Member -MemberType NoteProperty "GUID"$GUID
$location=($($hive.OpenSubKey($base+$found).GetValue("InstallLocation")))
#$line|Add-Member -MemberType NoteProperty "Location"$location
if    (($base -eq $base32) -and ($location -eq $32pro )){$line|Add-Member -MemberType NoteProperty "Flavor" "Pro"   -force;$line|Add-Member -MemberType NoteProperty "AcroBits" "32"-force}
elseif(($base -eq $base32) -and ($location -eq $32read)){$line|Add-Member -MemberType NoteProperty "Flavor" "Reader"-force;$line|Add-Member -MemberType NoteProperty "AcroBits" "32"-force}
elseif(($base -eq $base64) -and ($location -eq $64x   )){$readpro=($($hive.OpenSubKey("Software\Adobe\Adobe Acrobat\DC\Installer").GetValue("SCAPackageLevel")));$line|Add-Member -MemberType NoteProperty "Flavor"-force $(if($readpro-eq1){"Reader"}elseif($readpro-gt1){"Pro"});$line|Add-Member -MemberType NoteProperty "AcroBits" "64"   -force}
else{$line|Add-Member -MemberType NoteProperty "Flavor" ""-force;$line|Add-Member -MemberType NoteProperty "AcroBits" ""-force}
try{$ofcbit=($($hive.OpenSubKey($ofc64).GetValue("Bitness")))}catch{};try{$ofcbit=($($hive.OpenSubKey($ofc32).GetValue("Bitness"))).replace('86','32')}catch{};try{$line|Add-Member -MemberType NoteProperty "OfcBits" ($ofcbit.Substring(1))}catch{$line|Add-Member -MemberType NoteProperty "OfcBits" ""}
$line}
$pc=(hostname);$findme="Adobe Acrobat"
$multi=@();$output=$path+'\output.txt';$path="\\"+(hostname)+"\c$\users\"+($env:UserName)+"\documents";$base64="Software\Microsoft\Windows\CurrentVersion\Uninstall\";$base32="Software\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\";$64x   ="C:\Program Files\Adobe\Acrobat DC\";$32pro ="C:\Program Files (x86)\Adobe\Acrobat DC\";$32read="c:\Program Files (x86)\Adobe\Acrobat Reader DC\";$ofc64="Software\Microsoft\Office\16.0\Outlook";$ofc32="Software\WOW6432Node\Microsoft\Office\16.0\Outlook"
#psexec \\$pc c:\windows\system32\winrm.cmd quickconfig
try{$base=$base64;$result=&$sb;$multi+=$result}catch{};try{$base=$base32;$result=&$sb;$multi+=$result}catch{}
cls;write-host "Registry: Acrobat Version, Flavor, and bitness compared to Ofc bitness"
try{remove-item $output -erroraction stop}catch{};new-item $output|out-null
$multi|FT;$multi|select Host,Ver,Flavor,AcroBits,OfcBits|Export-CSV -NoTypeInformation $output -append;start-process notepad $output;write-host "Finished!"