powershell remote - powershell

I am trying to run this from my local machine (Win7 & PS v2.0)
cls
$sess = Enter-PSSession -ComputerName blmcrmpoc
Invoke-Command -Session $sess -Scriptblock
{
$path = "C:\inetpub\logs\LogFiles"
$lastWrite = (Get-Date).AddDays(-90)
$oldLogs = Get-ChildItem -path $path -Recurse -Filter *.log | Where {$_.LastWriteTime -le $lastWrite}
if ($oldlogs.count -gt 0)
{foreach ($log in $oldLogs)
{$log.Delete()}}}
But I get this error.
***Invoke-Command : Cannot validate argument on parameter 'Session'. The argument is null or empty. Supply an argument that is not null or empty and then try the command again.
What am I missing?

Try:
cls
$sess = New-PSSession -ComputerName blmcrmpoc
Invoke-Command -Session $sess -Scriptblock
{
$path = "C:\inetpub\logs\LogFiles"
$lastWrite = (Get-Date).AddDays(-90)
$oldLogs = Get-ChildItem -path $path -Recurse -Filter *.log | Where {$_.LastWriteTime -le $lastWrite}
if ($oldlogs.count -gt 0)
{
foreach ($log in $oldLogs)
{
$log.Delete()
}
}
}
With Enter-PSSession you ENTER a pssession(you start remoting it so you could write the commands directly as if it were the local machine). If you want to use Invoke-Command on a specific session, you create the session using New-Session because this creates a session without entering it.

How to access argument list for scriptblock
ArgumentList is based on use with scriptblock commands, like:
you have to pass arugments for script block..
cls
$sess = Enter-PSSession -ComputerName blmcrmpoc
Invoke-Command -Session $sess -Scriptblock
{
$path = "C:\inetpub\logs\LogFiles"
$lastWrite = (Get-Date).AddDays(-90)
$oldLogs = Get-ChildItem -path $path -Recurse -Filter *.log | Where {$_.LastWriteTime -le $lastWrite}
if ($oldlogs.count -gt 0)
{
$xArgvalue= arg[0]
$yArgvalue= arg[1]
foreach ($log in $oldLogs)
{
$log.Delete()
}
}
} -ArgumentList $x,$y
I found on below link how to pass arguments to script block

Related

Powershell - run script on multiple computers simultaneously

I'm working on a script that cleanup old user account and some data from computers.
I would like to run the script on 5 computers at one time from the attached list of PCs.
Is it possible? If so, how can it be done?
[CmdletBinding()]
Param(
[Parameter(Mandatory=$true)]
[string]$host_path = 'Host path'
)
$computer = Get-Content "$host_path"
foreach ($computer in $computer){
Invoke-Command -ComputerName $computer -ScriptBlock { Get-WMIObject -class Win32_UserProfile | Where {(!$_.Special) -and ($_.ConvertToDateTime($_.LastUseTime) -lt (Get-Date).AddDays(-30))}| Remove-WmiObject }
Invoke-Command -ComputerName $computer -ScriptBlock { Remove-Item -Path C:\Windows\ccmcache\* -Confirm:$false -Force -Recurse -Debug }
Invoke-Command -ComputerName $computer -ScriptBlock { Remove-Item -Path C:\ProgramData\1E\NomadBranch\* -Confirm:$false -Force -Recurse -Debug }
}
You can pass multiple computer names to Invoke-Command at once to achieve this:
[CmdletBinding()]
Param(
[Parameter(Mandatory=$true)]
[string]$host_path = 'Host path'
)
$computerNames = Get-Content $host_path
Invoke-Command -ComputerName $computerNames -ScriptBlock {
Get-WMIObject -class Win32_UserProfile | Where {(!$_.Special) -and ($_.ConvertToDateTime($_.LastUseTime) -lt (Get-Date).AddDays(-30))}| Remove-WmiObject
Remove-Item -Path C:\Windows\ccmcache\* -Confirm:$false -Force -Recurse -Debug
Remove-Item -Path C:\ProgramData\1E\NomadBranch\* -Confirm:$false -Force -Recurse -Debug
}
If you want to "chunk" the list of computer names into batches on N machines at a time, you can do it like this:
$computerNames = Get-Content $host_path
$batchSize = 5
while($computerNames.Count -gt 0){
# Pull the first N names from the list
$nextBatch = #($computerNames |Select -First $batchSize)
# Then overwrite the list with any elements _after_ the first N names
$computerNames = #($computerNames |Select -Skip $batchSize)
Write-Host "Executing remote command against $($nextBatch.Count) computers: [$($nextBatch.ForEach({"'$_'"}) -join ', ')]"
# Invoke remoting command against the batch of computer names
Invoke-Command -ComputerName $nextBatch -ScriptBlock {
Get-WMIObject -class Win32_UserProfile | Where {(!$_.Special) -and ($_.ConvertToDateTime($_.LastUseTime) -lt (Get-Date).AddDays(-30))}| Remove-WmiObject
Remove-Item -Path C:\Windows\ccmcache\* -Confirm:$false -Force -Recurse -Debug
Remove-Item -Path C:\ProgramData\1E\NomadBranch\* -Confirm:$false -Force -Recurse -Debug
}
}
If you are using PowerShell 7.x, you can do the following.
[CmdletBinding()]
Param(
[Parameter(Mandatory=$true)]
[string]$host_path = 'Host path'
)
# The default value for ThrottleLimit is 5, but I put it here to show syntax.
# Throttle is the number of concurrent runspaces to use. (ex: do 5 objects at a time)
Get-Content $host_path | Foreach-Object -ThrottleLimit 5 -Parallel -ScriptBlock {
Invoke-Command -ComputerName $_ -ScriptBlock {
Get-WMIObject -class Win32_UserProfile | Where-Object {(!$_.Special) -and ($_.ConvertToDateTime($_.LastUseTime) -lt (Get-Date).AddDays(-30))}| Remove-WmiObject
Remove-Item -Path C:\Windows\ccmcache\* -Confirm:$false -Force -Recurse -Debug
Remove-Item -Path C:\ProgramData\1E\NomadBranch\* -Confirm:$false -Force -Recurse -Debug
}
}
This will run X loops at a time, X being your -ThrottleLimit value, which defaults to 5.
Again, this is only available in PowerShell 7, and not backwards compatible with Windows PowerShell.

Check if process is running on multiple remote computers and copy a file if process is NOT running, keep list of those which succeeded

I need to copy a file to multiple computers, but can only do so if a particular app (process) is not running.
I know I can use Invoke-Command to run a script (scriptblock) on a list of machines.
But how can I check if process is running on the machine and then only copy file if it is not running.
So that at the end of running against a load of computers I can easily see those which succeeded e.g. process was not running and file was copied
Thanks
UPDATE:
I am assuming something like this will do the first bits of what I am asking, but how to visually show or log success or failure so I know which computers have been done - doesn't need to be anything fancy, even if simply a variable that holds computername of those where process wasn't running and file was copied okay
Invoke-Command -ComputerName PC1, PC2, PC3 -ScriptBlock {
If ((Get-process -Name notepad -ea SilentlyContinue) -eq $Null){
Copy-Item -Path "\\server01\c$\test\file.txt" -Destination "C:\test\file.txt" -Force
}
}
$Procs = invoke-command -ComputerName PC1 { get-process | Select Name }
If($Procs -notmatch "Notepad"){ Copy-Item -Path "\\server01\c$\test\file.txt" -Destination "\\$PC1\c$\test\" -Force}
edited:
$computers = #("PC1","PC2","PC3")
Foreach($computer in $computers){
$Procs = invoke-command -ComputerName $computer { Get-Process Notepad -ErrorAction SilentlyContinue}
If(!$Procs){"$Computer - not running Notepad"; Copy-Item -Path "\\server01\c$\test\file.txt" -Destination "\\$computer\c$\test\" -Force}
elseif($Procs){"$Computer - is running Notepad"}
}
Edit2(for clean output):
$computers = #("PC1","PC2","PC3")
$RNote = #()
$NNote = #()
$off = #()
Foreach($computer in $computers){
$TestC = Test-Connection -ComputerName $computer -Count 1
If(!($TestC)){$off += $computer} Else{
$Procs = invoke-command -ComputerName $computer { Get-Process Notepad -ErrorAction SilentlyContinue}
If(!$Procs){$NNote +=$computer; Copy-Item -Path "\\server01\c$\test\file.txt" -Destination "\\$computer\c$\test\" -Force}
elseif($Procs){$RNote +=$computer}
}
}
$leng =[array]$RNote.count,$NNote.Count,$off.count
[int]$max = ($leng | measure -Maximum).Maximum
for($i=0; $i -lt $max;$i++){
[pscustomobject]#{
"Notepad On" = $(if ($RNote[$i]){$RNote[$i]})
"Notepad Off" = $(if ($NNote[$i]){$NNote[$i]})
"Offline " = $(if ($off[$i]){$off[$i]})
}
}
I think this is what you're looking for or at least close:
$Results = #()
$Results +=
Invoke-Command -ComputerName DellXPS137000, DellXPS8920 -ScriptBlock {
$GPArgs = #{Name = "Notepad++"
ErrorAction = "SilentlyContinue"}
If ( $Null -ne (get-process #GPArgs )) {
#Process your copy here
$Status = "Success"
}
Else {$Status = "Failed"}
$Machine =
(Get-CimInstance -ClassName 'Win32_OperatingSystem').CSName
Return ,"$Machine : $Status"
}
Value of $Results:
PS> $results
DELLXPS137000 : Success
DELLXPS8920 : Failed
HTH

Powershell retrieving cert by Thumbprint as string versus string variable

I'm trying to piece together some PowerShell code to loop through a list of servers, return some info regarding their IIS sites and bindings, and if they have an https binding, get the certificateHash and use that locate the cert by thumbprint and return its expiration date.
The problem I am having is, when i run my code below $binding.cerficateHash seems to return what I would expect, a string of the cert Hash, but when I use that certificateHash property to try and get the cert by its thumbprint, it doesnt work... but when I take the raw string value of the certificateHash value and hardcode it, it works...
I've inspected the certificateHash.GetType() and it appears to be just a string, so i dont understand what im doing wrong, and ive tried a handful of things, with no avail, granted this is my first crack at powershell so there's lots I don't know.
$sites = Invoke-Command -ComputerName $serverName { Import-Module WebAdministration; Get-ChildItem -path IIS:\Sites } -ErrorAction SilentlyContinue
foreach($site in $sites)
{
$serverName
$site.name
$site.physicalPath
foreach($binding in $site.bindings.Collection)
{
$binding.protocol
$binding.bindingInformation
$binding.certificateHash
$binding.certificateStoreName
if($binding.certificateHash)
{
# This outputs AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
$binding.certificateHash
# this retrieves a cert and returns its expiration date, Woohooo!
Start-Job Invoke-Command -ComputerName $serverName -ScriptBlock { (Get-ChildItem -path Cert:\LocalMachine\WebHosting | Where-Object {$_.Thumbprint -eq "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" })[0].GetExpirationDateString() }
# this does not find a cert, and ive tried many things, and no dice.
Start-Job Invoke-Command -ComputerName $serverName -ScriptBlock { (Get-ChildItem -path Cert:\LocalMachine\WebHosting | Where-Object {$_.Thumbprint -eq $binding.certificateHash })[0].GetExpirationDateString() }
# i've tried extracting the hash via "tostring" and using that, no dice
$hash = $binding.certificateHash.ToString()
Start-Job Invoke-Command -ComputerName $serverName -ScriptBlock { (Get-ChildItem -path Cert:\LocalMachine\WebHosting | Where-Object {$_.Thumbprint -eq $hash })[0].GetExpirationDateString() }
# i've tried adding some wildcards and using the -like operator, no dice.
$hash = "*" + $binding.certificateHash + "*"
Start-Job Invoke-Command -ComputerName $serverName -ScriptBlock { (Get-ChildItem -path Cert:\LocalMachine\WebHosting | Where-Object {$_.Thumbprint -lilke $hash })[0].GetExpirationDateString() }
}
}
}
Example output for a site.
Site1
D:\Apps\site1
http
*:80:Site1-test.ourdomain.com
https
*:443:Site1-test.ourdomain.com
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
WebHosting
The computer you invoke the script block on doesn't know about the $binding variable in your local session. (That's also why it works when passing a literal string.)
Try passing the value as argument:
Invoke-Command -Computer $serverName -Script {
param ($hash)
(gci Cert:\LocalMachine\WebHosting | ? Thumbprint -eq $hash)[0].GetExpirationDateString()
} -Arg $binding.certificateHash

Get-content Pipe to Select String and retrieve ComputerName

I'm trying to write a script that I can look at a list of remote servers, search for certain file type. Then if the File consists with a certain string i want to know what file that is and on what computer.
I started with the basics but when I do Select-String I can't retrieve the file nor can i find the computer name, it just outputs the same string as i have in my script.
I'm sure i'm missing something basic but any suggestions here will greatly be appreciated.
$servers = ArrayofServers
ForEach ($server in $Servers){
invoke-command -computername $server {Get-ChildItem "D:\Folder\Location\Windows\" -Include *.txt -Recurse |
Get-Content |
Select-String 'String to Select' |
Out-String
} -Credential $credential }
I want to be able to Select FileName,Pscomputername and Select-String.
I find it easier to break the pipeline. I have tested this code successfully:
$Servers = #("ArrayofServers")
foreach ($Server in $Servers)
{
Invoke-Command -ComputerName $Server -ScriptBlock {
$Results = $false
$SearchResults = Get-ChildItem "D:\Folder\Location\Windows\" -Include *.txt -Recurse
foreach ($SearchResult in $SearchResults)
{
If ($SearchResult | Get-Content | Select-String 'String to Select')
{
$SearchResult.FullName
$SearchResult | Get-Content
$Results = $true
}
}
If ($Results)
{
$env:COMPUTERNAME
}
} -Credential $Credential
}

Global scope variable is null within Scriptblock

Running the following code produces an error because the variable used for path is resolved as null event though it defined in the script:
$ServerName = "test01"
$RemotePath = "C:\Test\"
$TestScriptBlock = { copy-item -Path $RemotePath -Destination C:\backup\ -Force -Recurse }
$CurrentSession = New-PSSession -ComputerName $ServerName
Invoke-Command -Session $CurrentSession -ScriptBlock $TestScriptBlock
How do I call the $RemotePath defined in the parent script from within the ScriptBlock? I need to use $RemotePath in other parts of the parent script. Note, this value doesn't change, so it can be a constant.
UPDATE -- WORKING SOLUTION
You have to pass in variable as parameter to the scriptblock:
$ServerName = "test01"
$RemotePath = "C:\Test\"
$TestScriptBlock = { param($RemotePath) copy-item -Path $RemotePath -Destination C:\backup\ -Force -Recurse }
$CurrentSession = New-PSSession -ComputerName $ServerName
Invoke-Command -Session $CurrentSession -ScriptBlock $TestScriptBlock -ArgumentList $RemotePath
You've got two scripts there, not one. The $TestScriptBlock is a separate script nested inside the main one, you send it to the remote computer, and that remote computer doesn't have $RemotePath configured. Try:
$ServerName = "test01"
$TestScriptBlock = {
$RemotePath = "C:\Test\"
copy-item -Path $RemotePath -Destination C:\backup\ -Force -Recurse
}
$CurrentSession = New-PSSession -ComputerName $ServerName
Invoke-Command -Session $CurrentSession -ScriptBlock $TestScriptBlock
(I would probably call it $LocalPath then, though)
Try this syntax:
$globalvariable1 = "testoutput01"
$globalvariable2 = "testoutput02"
$Scriptblock = {
Write-Host $using:globalvariable1
Write-Host $using:globalvariable2
}
$serverName = Domain\HostNameofServer
Invoke-Command -ComputerName $serverName -ScriptBlock $ScriptBlock -ArgumentList $globalvariable1, $globalvariable2
The execution happens in a different session and all the variables in the current scope are not copied to the remote session by default.
You can use either parameters or "using:" as explained here:
How can I pass a local variable to a script block executed on a remote machine with Invoke-Command?