PowerShell through Group Policy - powershell

I have two separate PowerShell (.ps1) files that I'd like to run one after the other when a user logs on to a PC. They're fairly straightforward tasks. The first copies a shortcut from a network location to all users AppData folder.
Copy-Item -Path "\\Server\Share\*.lnk" -Destination "$env:APPDATA\Microsoft\Windows\Start Menu\Programs"
The second .ps1 file removes a load of bloatware from Windows 10, I won't put all the code in here as it's quite repetitive but it basically lists a load of apps and finally removes them.
$AppList = #(
"*Microsoft.3dbuilder*"
"*AdobeSystemsIncorporated.AdobePhotoshopExpress*"
"*Microsoft.WindowsAlarms*"
"*Microsoft.Asphalt8Airborne*"
)
foreach ($App in $AppList) {
Get-AppxPackage -Name $App | Remove-AppxPackage -ErrorAction SilentlyContinue
}
If I place the two files into the same logon policy, the first script will run but the second one doesn't until the user logs off and back on again (I'd like them both to run at the same time).
I've tried placing them both in the same file and separating then with a ;, this didn't work so tried and, again no joy. I've also tried creating a master file (with the two .ps1 files in the same location) and running the following, again this didn't work.
&"$PSScriptroot\Copy Devices and Printers Shortcut.ps1" &"$PSScriptroot\BloatwareRemoval.ps1"
I've also tried separating the above with ; and and with no joy.
Edit I've resolved this with the following pd1 file:
Get-ChildItem \\File\Location | ForEach-Object {
& $_.FullName
}

As per the comments, this script you should save as a .ps1 file and call it however you want. It will do both the operations together. I have added error handling but ideally you should parse them in a file so that you can refer in case of any failure.
If( (Test-Path -Path "\\Server\Share\*.lnk") -and (Test-Path -Path "$env:APPDATA\Microsoft\Windows\Start Menu\Programs"))
{
Copy-Item -Path "\\Server\Share\*.lnk" -Destination "$env:APPDATA\Microsoft\Windows\Start Menu\Programs"
}
else
{
"Invalid path. Kindly validate. "
}
#("*Microsoft.3dbuilder*","*AdobeSystemsIncorporated.AdobePhotoshopExpress*","*Microsoft.WindowsAlarms*","*Microsoft.Asphalt8Airborne*")|% {
try{
Get-AppxPackage -Name $_ | Remove-AppxPackage -ErrorAction stop
}
catch
{
$_.Exception.Message
}
}
Hope it helps.

I've resolved this with the following pd1 file:
Get-ChildItem \\File\Location | ForEach-Object {
& $_.FullName
}

Related

Remove alternative data stream using powershell

I'm trying to remove a bunch of OSX alternate data streams on an NTFS volume. However no matter what I try I cannot get Powershell to do it. Yes, I admit that my powershell is not great. Is anyone able to help?
Objective: Remove the ADS "AFP_AfpInfo" from any directory in the volume.
Current Code:
Get-ChildItem E:\ -Directory -Recurse | ForEach-Object {
$streams = Get-Content -Path $_ -Stream AFP_AfpInfo -ErrorAction SilentlyContinue
if ($streams) {
$streams | ForEach-Object {
try {
Remove-Item -Path "$($_.PSPath)" -Stream AFP_AfpInfo -Recurse -Force -ErrorAction Silentlycontinue
}
catch {
Write-Host "An error occurred: $($_.Exception.Message)"
}
}
}
}
Current error:
An error occurred: A parameter cannot be found that matches parameter name 'Stream'.
Note: Running Powershell 7.3
-Recurse and -Stream don't seem to go together even though in the documentation they appear in the same Parameter Sets. In this case -Recurse should be removed. GitHub Issue #9822 was submitted to add clarification to the Remove-Item doc.
Also, you're seeking for an exact stream, AFP_AfpInfo, so I don't see a need to enumerate $streams. Lastly, checking if a file or folder has an alternative stream should be done with Get-Item instead of Get-Content for efficiency.
As a final aside, the code must use the .Remove method from EngineIntrinsics to work, Remove-Item -Confirm:$false -Force will always ask for confirmation on folders, arguably a bug. Remove-Item should skip confirmation checks if -Stream is in use and -Confirm:$false -Force. GitHub issue #19154 was submitted to follow up on this.
$removeFunc = $ExecutionContext.InvokeProvider.Item.Remove
$targetStream = 'AFP_AfpInfo'
Get-ChildItem E:\ -Recurse -Directory | ForEach-Object {
if ($stream = $_ | Get-Item -Stream $targetStream -ErrorAction SilentlyContinue) {
try {
$removeFunc.Invoke($stream.PSPath, $false, $true, $true)
}
catch {
Write-Host "An error occurred: $($_.Exception.Message)"
}
}
}
Why are you not just using the Unblock-File cmdlet to remove ADS?
https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/unblock-file?view=powershell-7.3
Description This cmdlet only works on the Windows and macOS platforms.
The Unblock-File cmdlet lets you open files that were downloaded from
the internet. It unblocks PowerShell script files that were downloaded
from the internet so you can run them, even when the PowerShell
execution policy is RemoteSigned. By default, these files are blocked
to protect the computer from untrusted files.
Before using the Unblock-File cmdlet, review the file and its source
and verify that it is safe to open.
Internally, the Unblock-File cmdlet removes the Zone.Identifier
alternate data stream, which has a value of 3 to indicate that it was
downloaded from the internet.
Get-Help -Name Unblock-FIle -Examples
NAME
Unblock-File
SYNOPSIS
Unblocks files that were downloaded from the internet.
------------------ Example 1: Unblock a file ------------------
PS C:\> Unblock-File -Path C:\Users\User01\Documents\Downloads\PowerShellTips.chm
-------------- Example 2: Unblock multiple files --------------
PS C:\> dir C:\Downloads\*PowerShell* | Unblock-File
------------- Example 3: Find and unblock scripts -------------
PS C:\> Get-Item * -Stream "Zone.Identifier" -ErrorAction SilentlyContinue
FileName: C:\ps-test\Start-ActivityTracker.ps1
See also Get-Item, Clear-Content and Remove-Item cmdlets use case:
Friday Fun with PowerShell and Alternate Data Streams
https://jdhitsolutions.com/blog/scripting/8888/friday-fun-with-powershell-and-alternate-data-streams
You could also just use the MSSysinternals tool to remove ADS as well in your PS code.
https://learn.microsoft.com/en-us/sysinternals/downloads/streams

Remove-Item -Force on NULL Filewatcher file removed 1000 files on my server, is this a Powershell bug? Or just my bad code?

So, I am developing a script using FileSystemWatcher similar to this one: https://powershell.one/tricks/filesystem/filesystemwatcher
I only use the Created event.
I then run the following code on the files that are "Created."
I met a really unexpected error when I ran this code on a file that was already removed by another piece of code. So basically, the "Remove-WrongFileType" function received a file that was NULL, just nothing. And then it just started deleting tons of different files on my server.
I run my script from C:\ and I obviously gave it to high rights. However, I find it really strange that when the $Path is Null, the script just finds files to remove. I've managed to fix this in my code, by checking first if the path to the file leads to something, however I want to learn what caused the script to crash this hard, and why the Get-ChildItem finds files when the $Path is a NULL file. I wonder if this could be some kind of bug in Powershell? (Most likely not.. but I wonder..)
Function Remove-WrongFileType {
Param (
[string]$Path
)
$Files = Get-ChildItem -Path $Path -Force -Recurse
foreach($file in $Files) {
if(-not (Assert-LegalFileType -File $file.FullName){
Remove-Item -Path $file.Fullname -Force
Add-ToLog -logString “File $file was removed because of illegal filetype”
}
}
}
Function Assert-LegalFileType {
Param (
[string]$File
)
if(Test-Path -Path $File -PathType Container){
return $true
}
$fileToCheck = Get-Item -Path $File
$ExtensionOfFile = $fileToCheck.Extension
foreach($type in $AllowedFiles){
if($ExtensionOfFile -match $type) {
return $true
}
}
}
So I looked up what happens when you pass NULL to Get-childitem. And it is a known issue apparently. Get-ChildItem -Path $null does not throw an error #8708
A comment describe the same issue:
One of the side effects of this bug/feature could be to accidentally delete your system when you are using the output of this command piped to say a $_.Delete(). That is exactly what happened when I refactored my code to delete previous test runs; so
From :
Get-ChildItem -Path C:\SourceCodeTLM\testRunResults-Include * -File -Recurse | foreach { $.Delete() }
To:
$testRunResults= "C:\SourceCodeTLM\testRunResults"
Get-ChildItem -Path $testRunResults-Include * -File -Recurse | foreach { $.Delete() }
and forgot to initialize the variable while doing a debug.
In the worst case, I expected an error but instead, the cmd ran and started deleting my current dir content (Which by default was PS C:\windows\system32>).
Before I could understand what happened and pressed ctrl+c; enough files were deleted to corrupt my system. I had to restore and all of my stuff on my machine was lost. I learned this lesson the hard way but maybe others don't have to :). May be giving an error (when null) or keeping this parameter (mandatory) would be better from a risk standpoint :).
So yeah, don’t pass null to get-childitem and try to force delete the output with high privileges.

How to wait for permission denied type erros over the network

In a script, I use Get-ChildItem -File -Recurse in order to check my permissions on subfolders. Then, I try to read the first character of every files. My goal is to catch Permission Denied type errors using $Error. It works well locally. But when I execute the script on a remote server with a UNC long path, the errors aren't generated. If I run manually the Get-ChildItem command just after the execution of the script, which is supposed to generate some errors, it display the files but does not generate errors. If I wait a few minutes and I run it again, I finally get the errors displayed.
Is there a way to wait for the errors to be generated?
Here is the specific part of my code which doesn't generate any error over the network:
# Check if the current item is a folder or a file
If($elem.Attributes -eq 'Directory')
{
# Get all child items of File type
$subElem = Get-ChildItem -LiteralPath $elem.FullName -File -Recurse -ErrorAction SilentlyContinue
# Parse subfolders and files to check permissions integrity. To generate an Permission Denied error, a file must be open
ForEach($subItem in $subElem)
{
# Read the first character of the current sub-item
Get-Content -LiteralPath $subItem.FullName -Encoding byte -TotalCount 1 -ErrorAction SilentlyContinue | Out-Null
}
}
Else
{
# Read the first character of the current element
Get-Content -LiteralPath $elem.FullName -Encoding byte -TotalCount 1 -ErrorAction SilentlyContinue | Out-Null
}
I finally found the solution myself.
In this script, I use the module NTFSSecurity (https://github.com/raandree/NTFSSecurity) in order to manage ACLs and inheritance. Over the network, it seems to be a bit slow.
Before the bit of code I shared above, I have a few lines which check and updates a bunch of ACLs over the network. As it takes some time, errors are only generated some time after. In this case, if the command encounter an error, it just continues but doesn't display or catch the error at the same time.
I used errors to detect items on which I had to recover the access. Now, I use another cmdlet coming with the NTFSSecurity module, Get-NTFSEffectiveAccess.
I wrote a little function which does perfectly the trick:
Function Check-MyAccess([String]$Path)
{
# Get effective permissions
$effectiveAccess = Get-NTFSEffectiveAccess -Path $Path -ErrorAction SilentlyContinue
# Check to be, at least, able to read the item
If(($effectiveAccess -eq $Null) -or ($effectiveAccess.AccessRights -Match 'Synchronize') -or ((($effectiveAccess.AccessRights -Like '*Read*') -or ($effectiveAccess.AccessRights -Like '*Modify*') -and ($effectiveAccess.AccessControlType -Match 'Deny'))))
{
Return $False
}
Else
{
Return $True
}
}

Create PS script to find files

I want to start by saying coding is a bit outside of my skill set but because a certain problem keeps appearing at work, I'm trying to automate a solution.
I use the below script to read an input file for a list of name, search the C:\ for those files, then write the path to an output file if any are found.
foreach($line in Get-Content C:\temp\InPutfile.txt) {
if($line -match $regex){
gci -Path "C:\" -recurse -Filter $line -ErrorAction SilentlyContinue |
Out-File -Append c:\temp\ResultsFindFile.txt
}
}
I would like to make two modifications to this. First, to search all drives connected to the computer not just C:\. Next, be able to delete any found files. I'm using the Remove-Item -confirm command but so far can't make it delete the file it just found.

Why is my PowerShell script writing blank lines to console?

I have a bit of an odd problem. Or maybe not so odd. I had to implement a "custom clean" for a PowerShell script developed for building some unique configurations for my current project (the whys are not particularly important). Basically it copies a bunch of files from the release directories into some temporary directories with this code:
$Paths = Get-ChildItem $ProjectPath -recurse |
Where-Object { ($_.PSIsContainer -eq $true) -and
(Test-Path($_.Fullname + 'bin\release')) } |
Select-Object Fullname
ForEach ($Path in $Paths)
{
$CopyPath = $Path.Fullname + '\bin\Temp'
$DeletePath = $Path.Fullname + '\bin\Release'
New-Item -ItemType directory -path $CopyPath
Copy-Item $DeletePath $CopyPath -recurse
Remove-Item $DeletePath Recurse
}
And after the build copies it back with:
ForEach ($Path in $Paths)
{
$CopiedPath = $Path.Fullname + '\bin\Temp\'
$DeletedPath = $Path.Fullname + '\bin\Release\'
$Files = Get-ChildItem $CopiedPath -recurse |
where-object {-not $_PSIsContainer}
ForEach ($File in $Files)
{
if(-not (Test-Path ($DeletedPath+$File.Name)))
{
Copy-Item $File.Fullname ($DeletedPath+$File.Name)
}
}
Remove-Item $CopyPath -recurse -force
}
This is pretty clunky and noobish (Sorry, I'm a PowerShell noob drinking from a fire hose), but it works for the purpose and I will clean it up later. However, when it executes the initial copy to the temp directories, it writes a lot of blank lines to the screen, which isn't ideal as I have a message I display while this process is executing to assure our CM doesn't freak out and think it broke, but this message is blown away by the blank lines. Do you know what might be causing this and how I might solve this? I'm using PowerShell 2.0 out of the box and due to the nature of this project I can't upgrade or get any outside libraries. Thanks guys.
If the only thing you're looking to do is clean up the console output, then all you need to do is use the pipeline. You can start the command with [void], which will exclude all information from the pipeline. You can also pipe the whole thing into the Out-Null cmdlet, which will trap all output, except for the lines that don't have output.
The New-Item cmdlet by default returns output to the console on my version of Windows PowerShell (4.0). This may not be true on previous versions, but I think it is... Remove-Item also doesn't return any output, usually. If I were to take a stab, I'd kill output on those lines that use the "Item" noun using one of the methods mentioned above.