Powershell ForEach-Object output - powershell

More of a basic question but I can't seem to get the syntax correct. I am trying to go through a list and then output the Computer Name and BDE status with it.
Get-Content 'C:\Users\Test\Test\IT\Lists\LaptopList.txt' | ForEach-Object { write-host "***" | manage-bde -status }
Is there something I need to put where the *'s are so that it correlates with the computer/object and the status?
Thanks!

Is there something I need to put where the *'s are so that it correlates with the computer/object and the status?
Yes! You're looking for $_:
Get-Content 'C:\Users\Test\Test\IT\Lists\LaptopList.txt' | ForEach-Object {
Write-Host $_
}
As the name indicates, Write-Host writes output directly to the host application - in the case of powershell.exe it writes directly to the screen buffer - which means it doesn't output anything to downstream cmdlets in a pipeline statement like the one you've constructed.
You'll therefore want two separate statements:
Get-Content 'C:\Users\Test\Test\IT\Lists\LaptopList.txt' | ForEach-Object {
Write-Host $_
manage-bde -status
}
Now, manage-bde is not a PowerShell cmdlet - it's a windows executable, and it doesn't support managing remote computers.
So we need something in PowerShell that can run manage-bde on the remote machine - my choice would be Invoke-Command:
Get-Content 'C:\Users\Test\Test\IT\Lists\LaptopList.txt' | ForEach-Object {
Write-Host "Remoting into $_ to fetch BitLocker Drive Encryption status
Invoke-Command -ComputerName $_ -ScriptBlock { manage-bde -status }
}
Here, we instruct Invoke-Command to connect to the computer with whatever name is currently assigned to $_, and then execute manage-bde -status on the remote machine and return the resulting output, if any. Assuming that WinRM/PowerShellRemoting is configured on the remote machines, and the user executing this code locally is a domain account with local admin privileges on the remote computers, this will work as-is.
Further reading:
about_Remote
about_Remote_Requirements
about_Remote_Troubleshooting

Related

Pulling Win10 activation codes from remote computers

I'm brand new to PS scripting, so bear with me :)
I'm trying to create a PS script that will write the Win10 activation code to a file then copy that file to a central repo to then manually activate.
I'm creating a PS script and trying to run
cscript.exe c:\windows\system32\slmgr.vbs -dti >
$SourceDir\$env:computername.txt
$SourceDir = \\computer01\c$\temp
I need to run it from one computer, remotely connecting to every computer on the network, creating the computername.txt file then copying that file back to a central repository for all the files.
What I have so far:
$s1=New-PSSession -ComputerName computer01 -Credential $AdminCred
Test-Connection -ComputerName computer01
$id='\\computer01\windows\system32'
$SourceDir='\\computer01\c$\temp'
md $SourceDir
$GetActID=cscript.exe $id\slmgr.vbs -dti >
$SourceDir\$env:computername.txt
Invoke-Command -Session $s1 -ScriptBlock { $Using:GetActID }
Then I call a batch file that copies the computername.txt file from the computer01 over to a repository where they are going to sit.
I FINALLY got it working correctly except for the name of the file isn't naming it to the computer01, it's naming it with the hostname of the computer I'm running it from, therefore the filenames are identical. I had the naming piece working, but I had to change the way I was remoting into the computer and now it's not naming correctly.
Any idea on how I could get it to name the file to be related to the remote computer?
**I'm still working on the whole piece of the puzzle where it goes back to an excel sheet pulled from AD and pulls the host names from that sheet to connect to each machine, I believe I'll be adding a ForEach syntax in there somehow for that.
Although not sure how you are getting the list of "every computer on the network", chances are you are doing this using
# get a list of all AD computers (their names only)
$computers = (Get-ADComputer -Filter *).Name
Then I think you don't need to have every computer save the file on its own disk and later copy these files to a central share.
Instead, just capture the info in a variable and after the loop write the file to the central share as structured CSV file combining all computernames and install id's so you can open in Excel.
Using the array of computernames from above, iterate through them
$result = $computers | ForEach-Object {
# test if the computer can be reached
if (Test-Connection -ComputerName $_ -Count 1 -Quiet) {
$installId = Invoke-Command -ComputerName $_ -ScriptBlock {
cscript.exe //nologo "$env:SystemRoot\System32\slmgr.vbs" -dti
}
# $installId is returned as array !
# output an object with two properties
[PsCustomObject]#{
Computer = $_
InstallId = $installId[0] -replace '\D' # remove everything non-numeric
}
}
else {
Write-Warning "Computer $_ is not responding"
}
}
# now you can display the result on screen
$result | Format-Table -AutoSize
# or by means of the GridView if you prefer
$result | Out-GridView -Title 'Computer InstallIds'
# and save the results in your central share as structured CSV file
$result | Export-Csv -Path '\\server\share\restofpath\ComputerInstallIds.csv' -NoTypeInformation
You may have to append -Credential $adminCreds to the Invoke-Command call to make sure you have permissions to have each machine run that piece of code in the scriptblock. The easiest way of obtaining that credential is to start off with $adminCreds = Get-Credential -Message "Please enter administrator credentials"

Remote command output to text file (remote system)

I know I must be using these commands wrong but I can't seem to find a solution. I believe the issue is with my use of the invoke-command and out-file. I'm trying to check to see if a process is running on multiple remote machines and write their states back to a text file on the host system. Even if it wrote to the remote system I could work with that but I cant seem to get anything.
$MyDomain=’mydomain’
$MyClearTextUsername=’myusername’
$MyClearTextPassword=’mypassword’
$MyUsernameDomain=$MyDomain+’\’+$MyClearTextUsername
$SecurePassword=Convertto-SecureString –String $MyClearTextPassword
-AsPlainText –force
$MyCredentials=New-object System.Management.Automation.PSCredential
$MyUsernameDomain,$SecurePassword
$Servers = ( "server1","server2","server3")
$output = foreach ($Server in $Servers)
{
$Session = New-PSSession -ComputerName $Server -Credential $MyCredentials
Invoke-Command -Session $Session -ScriptBlock
{
Get-Service -Name service | select name, status, PSComputername, Runspaceid
} | Out-File -filepath 'c:\TEMP\check.txt'
}
Write-output $output | Out-File -filepath 'c:\TEMP\check.txt'
edit: I don't believe the last line is needed but I threw it in just to see if I could get anything out.
You are not capturing anything in $output because you are redirecting all of the output from your Invoke-Command to Out-File -filepath 'c:\TEMP\check.txt'. Get-Service doesn't return that much data, especially once's it's been deserialized when it returns from the remote session, so I wouldn't bother with the Select statement. Even if you do want to include the Select statement you are specifying PSComputerName which doesn't get added until the data comes back from the remote system, so you may want to move that Select to outside of the scriptblock and after the Invoke-Command in the pipeline. Plus, since you are outputting with Out-File your local file is being overwritten each time that call is made, so the first server's results are saved, then overwritten by the second server's results, then by the third server's results. After that, since $output has nothing (as all output was redirected to file), you are outputting an empty variable to the same file, effectively erasing the service state of the third server.
But this really all becomes a moot point if the script is run with the credentials that has access to the remote servers. You can specify one or more computer names to the Get-Service cmdlet, so this could become as simple as:
$Results = Get-Service Service -ComputerName 'Server1','Server2','Server3'
$Results | Select name, status, PSComputername, Runspaceid | Set-Content 'C:\TEMP\check.txt'
Just to make sure... you are looking for a service right? Not just a process? Because if it isn't a service you would need to use Get-Process instead of Get-Service.
If you want to output the data to the remote server you could do:
$output = foreach ($Server in $Servers)
{
$Session = New-PSSession -ComputerName $Server -Credential $MyCredentials
Invoke-Command -Session $Session -ScriptBlock
{
Get-Service -Name service | Tee-Object -FilePath C:\Temp\ServiceState.txt
} | select name, status, PSComputername, Runspaceid
}
$output | Out-File -filepath 'c:\TEMP\check.txt'
That should make a file in the C:\Temp folder on each server with the state of the service, as well as pass the information back to the local host, where it is passed to Select and stored in $output. At the end I output $output to file, just as you did.
I guess in the end you could just remove the Out-File call from within your loop, and it would probably do what you want it to.

If Else within an Invoke-Command Powershell

I am trying to run the below command to say if this registry key exists then Get-ItemProperty Else Do nothing or Display Text for Testing.
"SQL Server Product Name" = Invoke-Command -ComputerName $Computer -ScriptBlock {If (Test-Path "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names") { Get-ItemProperty HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\* | Where-Object -FilterScript { (($_.Publisher -like "Microsoft*") -and ($_.DisplayName -like "Microsoft SQL Server*(*-bit)")) } | Select-Object -first 1 -ExpandProperty DisplayName } else {Write-Host "Blah"}}
The Else doesn't seem to do anything because right now if the reg key does not exist, it puts in {} to the results instead of Blah. I am not exactly sure if the If statement is working at all since I think it may just be running the Get-ItemProperty no matter what since if that path exists, I get the expected results.
The issue with your example is that the Else scriptblock uses Write-Host. When you execute this on a remote machine, the Host is a PowerShell session on that remote computer. You're writing the text to a session with no GUI on a remote machine.
To fix this, just remove the Write-Host cmdlet. The quoted text will be passed back to your local session along with anything else output by the scriptblock when it is executed on the remote session.

Powershell Deployed via SCCM Issue

I am writing a powershell script to be deployed by SCCM via a package. The aim of this is to remove an account with a specific name then write to a file stating if the account exists or not. The code is below:
$Computer = hostname
foreach ($C in $Computer) {
if (Test-Connection $C -Quiet) {
Write-Verbose "$C > Online"
$Users = Get-WMIObject Win32_UserAccount -Filter "LocalAccount=True" -ComputerName $C
if ($Users.Name -contains 'test') {
Add-Content \\SERVERNAME\SHARENAME.$\$computer-found_$(get-date -Format yyyymmdd_hhmmtt).txt "User 'test' found, Disable 'test' found"
net user test /active:no }
else {
Add-Content \\SERVERNAME\SHARENAME.$\$computer-notfound_$(get-date -Format yyyymmdd_hhmmtt).txt "User 'test' not found"
}
}
else {
Write-Verbose "$C > Offline"
}
}
I have also tried replace Write-Verbose with Write-Host and Add-Content with Out-File but the problem I having is that no content / file is created when I use the full network path or share e.g. \\SERVERNAME\SHARENAME.$ the path identified has all the correct permissions and is being ran locally using the System account.
I wanted to see if the issue occured when writing the file locatlly consequently this does not happen when written to C:\Temp\
Does anyone have any ideas on to solve this.
I don't think that local system account has access to a network resource. I'm not sure if you have ever configured it or not. And what the command you used to run the command
Here I post a working way of doing this using Configuration Manager deployment after testing in my lab.
Basically I created a package with source files
and created a task sequence with single "Run Command Line" step.
The reason I use a task sequence is because I want to use an account to access the txt file on the network, which I can configure within a task sequence. I don't think Local System Account have such permission.
The script (DeactivateTest.ps1) I use as below just like what you provided and changed a little on the logic:
$Computer = hostname
foreach ($C in $Computer) {
if (Test-Connection $C -Quiet) {
Write-host "$C > Online"
$Users = Get-WMIObject Win32_UserAccount -Filter "LocalAccount=True" -ComputerName $C
$result=0
Foreach($user in $Users){
if ($User.Name -like '*test*') {
$username = $user.Name
"`n$(get-date -Format yyyymmdd_hhmmtt) User $username found ON $C, Disable 'test'" | Add-Content \\cas\resource\Result.txt
net user $username /active:no
$result+=1
}}
if($result =0){
"`n$(get-date -Format yyyymmdd_hhmmtt) User 'test' not found ON $C" | Add-Content \\cas\resource\Result.txt}
}
else {
"`n$C is Offline" | Add-Content \\cas\resource\Result.txt
}
}
The script query local account and disable accounts which have words "Test" in the name. If you don't like this logic, you can change :).
\\cas\resource\Result.txt is a txt file on the network share. Clients will write result to this txt file.
The command in the task sequence is (it's a x64 machine):
PowerShell.exe -ExecutionPolicy Bypass -File ".\DeactiveTest.ps1"
The output is like:
I may get downvoted for this as my answer isn't technically directly answering your question, it is, however, intended to try and point you in what may be a more logical direction. All apologies if I offend anyone, but here it is:
Why not just disable the user using Group Policy? If you really want to know where the user is/isn't disabled then you could just use hardware inventory for that, but GP really is the best way to enforce this kind of setting.

test-connection that supports wildcard? workaround?

trying to see if anyone has a known workaround for using the test-connection cmdlet in powershell to ping wildcard entries in DNS.
I'm trying to clean out our DNS db and exported a list from our BIND server and am in the process of just pinging through the 600+ machines to see if anything responds. I made my own simple script but have also found one that works slightly better on this forum. The script works but the cmdlet help files state that the -computername parameter does not support wildcards and sure enough, when i run the script all CNAME records are reporting down/false when they actually should be responding. The code I'm using is below and is kind of messy but I just needed something quick and it works, but I've included it below for reference:
Get-Content -path C:\Work\testy.txt | ForEach-Object { Test-Connection -ComputerName $_ -Count 1 -AsJob } | Get-Job | Receive-Job -Wait | Select-Object #{Name='ComputerName';Expression={$_.Address}},#{Name='Reachable';Expression={if ($_.StatusCode -eq 0) { $true } else { $false }}} |out-file -FilePath c:\work\TEST.txt
As pointed out by briantist, any non-existing record name will do. You could generate a GUID to substitute the * in your record name:
"subdomain.domain.tld","*.domain.tld" |ForEach-Object {
Test-Connection -ComputerName $($_ -replace '\*',"$([guid]::NewGuid())")
}
Your expression for whether it's "Reachable" or not can be simplified as well:
#{Name='Reachable'; Expression={[bool]($_.StatusCode -eq 0)}}