I have the following PowerShell script:
param([switch]$Elevated)
function Test-Admin
{
$currentUser = New-Object Security.Principal.WindowsPrincipal $([Security.Principal.WindowsIdentity]::GetCurrent())
$currentUser.IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)
}
if ((Test-Admin) -eq $false) {
if ($elevated) {
# tried to elevate, did not work, aborting
} else {
Start-Process powershell.exe -Verb RunAs -ArgumentList ('-noprofile -noexit -file "{0}" -elevated ' -f ($myinvocation.MyCommand.Definition))
}
exit
}
function UpdateHosts {
param ($hostName)
Write-Host $hostName
try {
$strHosts = (Get-Content C:\WINDOWS\system32\drivers\etc\hosts -Raw)
if([string]::IsNullOrEmpty($strHosts)) {
Write-Error "Get-Content hosts empty"
exit
}
} catch {
Write-Error "Unable to read hosts file"
Write-Error $_
exit
}
try {
$strHosts -replace "[\d]+\.[\d]+\.[\d]+\.[\d]+ $hostName","$ipAddress $hostName" | Set-Content -Path C:\WINDOWS\system32\drivers\etc\hosts
} catch {
Write-Error "Unable to write hosts file"
Write-Error $_
exit
}
}
$ipAddress = "127.0.0.1"
UpdateHosts -hostName local.pap360.com
Sometimes, when I run it, I get the following error:
Set-Content : The process cannot access the file 'C:\WINDOWS\system32\drivers\etc\hosts' because it is being used by another process.
When I open up C:\WINDOWS\system32\drivers\etc\hosts in Notepad it's then blank. ie. all the data I had in it is wiped.
My question is... how can I prevent this from happening?
Like if Set-Content can't access the hosts file to write to it then how is it able to wipe it's contents? And why isn't the catch block working?
Here's the full error:
Set-Content : The process cannot access the file 'C:\WINDOWS\system32\drivers\etc\hosts' because it is being used by
another process.
At C:\path\to\test.ps1:36 char:92
+ ... $hostName" | Set-Content -Path C:\WINDOWS\system32\drivers\etc\hosts
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : WriteError: (C:\WINDOWS\system32\drivers\etc\hosts:String) [Set-Content], IOException
+ FullyQualifiedErrorId : GetContentWriterIOError,Microsoft.PowerShell.Commands.SetContentCommand
I also don't understand why it's so intermittent. Is there some Windows process that opens the hosts file up for 1s once a minute or some such?
First of all, check if your Firewall or AV software isn't restricting access to the file.
If that is not the case and 'some' other process is currently locking the hosts file, perhaps add a test before reading or writing the file can help:
function Test-LockedFile {
param (
[parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
[Alias('FullName', 'FilePath')]
[ValidateScript({Test-Path $_ -PathType Leaf})]
[string]$Path
)
$file = [System.IO.FileInfo]::new($Path)
# old PowerShell versions use:
# $file = New-Object System.IO.FileInfo $Path
try {
$stream = $file.Open([System.IO.FileMode]::Open,
[System.IO.FileAccess]::ReadWrite,
[System.IO.FileShare]::None)
if ($stream) { $stream.Close() }
return $false # file is not locked
}
catch {
return $true # file is locked
}
}
Then use like this:
function UpdateHosts {
param ($hostName)
Write-Host $hostName
$path = 'C:\WINDOWS\system32\drivers\etc\hosts'
# test if the file is readable/writable
# you can of course also put this in a loop to keep trying for X times
# until Test-LockedFile -Path $path returns $false.
if (Test-LockedFile -Path $path) {
Write-Error "The hosts file is currently locked"
}
else {
try {
$strHosts = (Get-Content $path -Raw -ErrorAction Stop)
if([string]::IsNullOrEmpty($strHosts)) {
Write-Error "Get-Content hosts empty"
exit
}
}
catch {
Write-Error "Unable to read hosts file:`r`n$($_.Exception.Message)"
exit
}
try {
$strHosts -replace "[\d]+\.[\d]+\.[\d]+\.[\d]+\s+$hostName", "$ipAddress $hostName" |
Set-Content -Path $path -Force -ErrorAction Stop
}
catch {
Write-Error "Unable to write hosts file:`r`n$($_.Exception.Message)"
exit
}
}
}
I'm trying to detect if a "Microsoft Word Document" has been configured "Read-Only" by MSOffice.
Everything I have read relates to checking using Get-ItemProperty "C:\tmp\readonly.docx" | Select-Object IsReadOnly, but that is checking if the File is "read only" from the filesystem level.
The Problem is Microsoft doesn't mark it on the outside, you'd need to open/check with the Microsoft COM object I figure to query if document is read only.
PS C:\Users\Admin> Get-ItemProperty "C:\tmp\readonly.docx" | Select-Object IsReadOnly
IsReadOnly
----------
False
Update: If file is configured RO without a Password then you can simple open as RW without a prompt (via powershell), but if it is with a Password then you get the prompt to acknowledge RO status which is what I want to avoid because it's hanging my script.
Continuing from my comment and note, not using anything dealing with the Word DOM via COM.
$File = 'd:\Temp\HSGCopy.docx'
# File not in use
Set-ItemProperty -Path $File -Name IsReadOnly -Value $false
(Get-ItemProperty 'd:\Temp\HSGCopy.docx').IsReadOnly
$File |
ForEach{
try
{
$TargetFile = (New-Object System.IO.FileInfo $PSitem).Open(
[System.IO.FileMode]::Open,
[System.IO.FileAccess]::ReadWrite,
[System.IO.FileShare]::None
)
$TargetFile.Close()
Remove-Item -Path $PSItem -WhatIf
}
catch [System.Management.Automation.ItemNotFoundException]{$PSItem.Exception.Message}
catch {$PSItem.Exception.Message}
}
# Results
<#
False
What if: Performing the operation "Remove File" on target "D:\Temp\HSGCopy.docx".
#>
# File in use
Set-ItemProperty -Path $File -Name IsReadOnly -Value $false
(Get-ItemProperty 'd:\Temp\HSGCopy.docx').IsReadOnly
$File |
ForEach{
try
{
$TargetFile = (New-Object System.IO.FileInfo $PSitem).Open(
[System.IO.FileMode]::Open,
[System.IO.FileAccess]::ReadWrite,
[System.IO.FileShare]::None
)
$TargetFile.Close()
Remove-Item -Path $PSItem -WhatIf
}
catch [System.Management.Automation.ItemNotFoundException]{$PSItem.Exception.Message}
catch {$PSItem.Exception.Message}
}
# Results
<#
False
Exception calling "Open" with "3" argument(s): "The process cannot access the file 'd:\Temp\HSGCopy.docx' because it is being used by another process."
#>
# Change the file attribute
# File not in use
Set-ItemProperty -Path $File -Name IsReadOnly -Value $true
(Get-ItemProperty 'd:\Temp\HSGCopy.docx').IsReadOnly
$File |
ForEach{
try
{
$TargetFile = (New-Object System.IO.FileInfo $PSitem).Open(
[System.IO.FileMode]::Open,
[System.IO.FileAccess]::ReadWrite,
[System.IO.FileShare]::None
)
$TargetFile.Close()
Remove-Item -Path $PSItem -WhatIf
}
catch [System.Management.Automation.ItemNotFoundException]{$PSItem.Exception.Message}
catch {$PSItem.Exception.Message}
}
# Results
<#
True
Exception calling "Open" with "3" argument(s): "Access to the path 'd:\Temp\HSGCopy.docx' is denied."
#>
# File in use
Set-ItemProperty -Path $File -Name IsReadOnly -Value $true
(Get-ItemProperty 'd:\Temp\HSGCopy.docx').IsReadOnly
$File |
ForEach{
try
{
$TargetFile = (New-Object System.IO.FileInfo $PSitem).Open(
[System.IO.FileMode]::Open,
[System.IO.FileAccess]::ReadWrite,
[System.IO.FileShare]::None
)
$TargetFile.Close()
Remove-Item -Path $PSItem -WhatIf
}
catch [System.Management.Automation.ItemNotFoundException]{$PSItem.Exception.Message}
catch {$PSItem.Exception.Message}
}
# Results
<#
True
Exception calling "Open" with "3" argument(s): "Access to the path 'd:\Temp\HSGCopy.docx' is denied."
#>
When using Word document protection
# with Word doc protection off
#>
$Word = New-Object –comobject Word.Application
Try
{
($Word.documents.open($File,$false,$false)).ReadOnly
Write-Warning -Message "$File is protected ReadOnly"
}
Catch {Write-Verbose -Message "$File is not protected" -Verbose}
# then don't forget to close
$Word.Quit()
# Results
<#
VERBOSE: d:\Temp\HSGCopy.docx is not protected
#>
# With Word doc protection on
$Word = New-Object –comobject Word.Application
Try
{
($Word.documents.open($File,$false,$false)).ReadOnly
Write-Warning -Message "$File is protected ReadOnly"
}
Catch {Write-Verbose -Message "$File is not protected ReadOnly" -Verbose}
# then don't forget to close
$Word.Quit()
# Results
<#
True
WARNING: d:\Temp\HSGCopy.docx is protected ReadOnly
#>
By accident or on purpose, you could have both set in an environment. I've had this happen to me in auto-classification scenarios. Meaning when FSRM/RMS/AIP has been deployed/implemented and enforced.
Update
Here a sample of what I have in my workflow to catch this sort of stuff, as per our exchange.
Clear-Host
$Files |
ForEach{
$File = $PSItem
"Processing $PSItem"
try
{
Write-Verbose -Message 'Word properties:
DocID, FullName, HasPassword,
Permission, ReadOnly, Saved,
Creator, CurrentRsid, CompatibilityMode' -Verbose
'DocID', 'FullName', 'HasPassword',
'Permission', 'ReadOnly', 'Saved',
'Creator', 'CurrentRsid', 'CompatibilityMode' |
ForEach {($Word.documents.open($File,$false,$false)).$PSitem}
Write-Verbose -Message 'File system ReadOnly attribute:' -Verbose
(Get-ItemProperty $File).IsReadOnly
Write-Verbose -Message 'Document state' -Verbose
$TargetFile = (New-Object System.IO.FileInfo $PSitem).Open(
[System.IO.FileMode]::Open,
[System.IO.FileAccess]::ReadWrite,
[System.IO.FileShare]::None
)
$TargetFile.Close()
Remove-Item -Path $PSItem -WhatIf
}
catch [System.Management.Automation.ItemNotFoundException]{$PSItem.Exception.Message}
catch {$PSItem.Exception.Message}
}
# Results
<#
Processing d:\Temp\HSGCopy.docx
VERBOSE: Word properties:
DocID, FullName, HasPassword,
Permission, ReadOnly, Saved,
Creator, CurrentRsid, CompatibilityMode
938207550
D:\Temp\HSGCopy.docx
False
True
True
1297307460
12414886
15
VERBOSE: File system ReadOnly attribute:
False
VERBOSE: Document state
What if: Performing the operation "Remove File" on target "D:\Temp\HSGCopy.docx".
#>
Ok, so to test this, I had 3 files as follows:
A) One regular test.docx with no restrictions
B) One readonly.docx w/ a password. This is the file that hung me up w/ a prompt
C) One nopass.docx w/ a read-only setting, but no PWD configured.
A and C would open regardless of the ReadOnly setting, but B would hang on a prompt even with DisplayAlerts set to 0. You also couldn't check the ReadOnly property unless the prompt was surpassed, or if you set it to TRUE it was always true obviosuly.
There is no way I found to check the ReadOnly or HasPassword property without first openeing document. You can likely inspect the XML file for HEX but I'd say that is less reliable. My way just took some testing/trickery to get working. The important part was I had to pass a password and catch if it failed. Doc's A/C would open fine even when you pass a password argument, so no harm there. In the catch I set ReadOnly = TRUE and Visible = TRUE. You may not need to set the visible part true, but if ReadOnly = TRUE then you can't make certain adjustments via VB (like ORIENTATION) and I'll be using SENDKEYS so I'll need the UI if ReadOnly = TRUE. As well, hiding the UI is just a "bonus" but not needed. I may just set it always visible if I continue wasting time on coding IF/THEN for the OPENUI statments.
Anyway... Here is a final code snippet to test on the three files which should result in each file opening w/o a prompt.
#Constants
Clear-Variable ReadOnly
$missing = [System.Type]::Missing
$str = ''
$PASSWD = 'IsPWDProtected?'
$wdAlertsNone = 0
$FILENAME = "C:\tmp\readonly.docx"
$OPENUI = "TRUE"
#Start Word
$ObjWord = New-Object -comobject Word.Application
IF ($OPENUI -eq "FALSE"){$ObjWord.Visible = $FALSE}ELSE{$ObjWord.Visible = $TRUE}
$ObjWord.Application.DisplayAlerts = $wdAlertsNone
#.Open
IF (!$ConfirmConversions){$ConfirmConversions = $missing}
IF (!$ReadOnly){$ReadOnly = $FALSE}
IF (!$AddToRecentFiles){$AddToRecentFiles = $missing}
IF (!$PasswordDocument){$PasswordDocument = $PASSWD}
IF (!$PasswordTemplate){$PasswordTemplate = $PASSWD}
IF (!$Revert){$Revert = $False}
IF (!$WritePasswordDocument){$WritePasswordDocument = $PASSWD}
IF (!$WritePasswordTemplate){$WritePasswordTemplate = $PASSWD}
IF (!$Format){$Format = 'wdOpenFormatAuto'}
IF (!$Encoding){$Encoding = $missing}
IF (!$Visible){$Visible = $False}
try{$ObjDoc=$ObjWord.documents.open($FILENAME,$ConfirmConversions,$ReadOnly,$AddToRecentFiles,$PasswordDocument,$PasswordTemplate,$Revert,$WritePasswordDocument,$WritePasswordTemplate,$Format,$Encoding,$Visible)}
catch {
Write-Error $_
Write-Host "Opening Read_Only"
$ReadOnly = $TRUE
$Visible = $TRUE
$ObjDoc=$ObjWord.documents.open($FILENAME,$ConfirmConversions,$ReadOnly,$AddToRecentFiles,$PasswordDocument,$PasswordTemplate,$Revert,$WritePasswordDocument,$WritePasswordTemplate,$Format,$Encoding,$Visible)
}
#AllDone?
PAUSE
$ObjWord.ActiveDocument.Close(0)
$ObjWord.Quit()
[gc]::collect()
[gc]::WaitForPendingFinalizers()
[gc]::collect()
[gc]::WaitForPendingFinalizers()
sleep 2
Result:
PS C:\Users\Admin> C:\tmp\test.ps1
C:\tmp\test.ps1 : The password is incorrect. Word cannot open the document. (C:\tmp\readonly.doc)
+ CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException
+ FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,test.ps1
Opening Read_Only
Press Enter to continue...:
Note: I hardcoded OPENUI = TRUE during testing because if it got hung on a prompt with the UI closed, I had to use tskill winword.exe and start over.
I am trying to create a script to create a specific folder and subfolders based on user input in SharePoint using Powershell.
I would like to be prompted for a name in a message box. (Message Box)
This would create a folder of that name in a specified path. (New-Item -Path c:(UserInput Variable) -ItemType Directory -Force)
Then create a specific set of subfolders in the newly created folder. For Loop (get-child item)
I would also like to prevent errors if the folder is already created. -Force Flag
I've been trying to look up how to do this, but i'm not sure how to put it all together.
Here's something that I would do, it's rough and simple. I've taken some functions that already existed here on StackOverflow. Hope this helps you.
Just a polite suggestion but next time, try to include as much of the script as you can even if it's nowhere near complete.
### VARS ###
$subdirs = "Subdir1","Subdir2","Subdir3","Subdir4"
### FUNCTIONS ###
# Stole this function from https://code.adonline.id.au/folder-file-browser-dialogues-powershell/
function Find-Folders {
[Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms") | Out-Null
[System.Windows.Forms.Application]::EnableVisualStyles()
$browse = New-Object System.Windows.Forms.FolderBrowserDialog
$browse.SelectedPath = "C:\"
$browse.ShowNewFolderButton = $false
$browse.Description = "Choose a parent directory"
$loop = $true
while($loop)
{
if ($browse.ShowDialog() -eq "OK")
{
$loop = $false
#Insert your script here
} else
{
$res = [System.Windows.Forms.MessageBox]::Show("You clicked Cancel. Would you like to try again or exit?", "Select a location", [System.Windows.Forms.MessageBoxButtons]::RetryCancel)
if($res -eq "Cancel")
{
#Ends script
return
}
}
}
$browse.SelectedPath
$browse.Dispose()
}
# Stole this function from https://stackoverflow.com/questions/30534273/simple-inputbox-function
Function Get-NewDirName (){
[void][Reflection.Assembly]::LoadWithPartialName('Microsoft.VisualBasic')
$title = 'New Directory'
$msg = 'New directory name:'
[Microsoft.VisualBasic.Interaction]::InputBox($msg, $title)
}
### MAIN ###
$parentDir = Find-Folders
if ($parentDir) {
#Get the new directory name
$NewDirName = Get-NewDirName
if ($NewDirName) {
#Creates the parent directory
New-Item -Path $parentDir\$NewDirName -ItemType Directory
#A parent directory was found
ForEach ($dir in $subdirs) {
New-Item -Path $parentDir\$NewDirName\$dir -ItemType Directory
}
} else {
Write-Error "No directory name was specified"
}
} else {
Write-Error "No parent directory was specified"
}
I have the below function running in a logon script, which checks whether the user has the current version of IT Self Help.exe. If the current version is not present, then it should be copied onto the desktop from the $appsource folder:
function UrgentSupportApp {
$ErrorActionPreference = "Stop"
trap {Log-Error $_ $MyInvocation.MyCommand; Return}
$desktop = $env:USERPROFILE + '\Desktop\'
$apptarget = $desktop + 'IT Self Help.exe'
$appsource = '\\file\Administration\Unused\Apps\IT Support App\IT Self Help.exe'
# Remove the old version of the app "IT Help Request.exe"
$oldapps = Get-ChildItem $desktop -Filter *"Help Request.exe"
if ($oldapps.count -gt 0) {Remove-Item $oldapps.PSPath -Force}
# Copy the new version over if it is not already present
$currentversion = (Get-Command $appsource).FileVersionInfo.FileVersion
if (Test-Path $apptarget) {
if ((Get-Command $apptarget).FileVersionInfo.FileVersion -ne $currentversion) {
Copy-Item $appsource $desktop -Force ##### Line 981 #####
}
} else {
Copy-Item $appsource $desktop -Force
}
}
function Log-Error {
param (
$error,
[string]$sub,
$detail
)
$ErrorActionPreference = "Stop"
trap {Log-Error $_ $MyInvocation.MyCommand; Return}
$filename = "\\file\administration\Unused\LogonScriptErrors\$username - $sub - $computername - $(Get-Date -Format ddMMyyyy-HHmmss).log"
New-Item $filename -ItemType File -Value "Message: `r`n`t $($error.Exception.Message) `r`n `r`nPosition: `r`n`t $($error.InvocationInfo.PositionMessage) `r`n `r`nSub: `r`n`t $sub `r`n `r`nDetail: `r`n`t $detail"
}
For a couple of users, I am seeing this error come through on line 981, char 22 (see the comment above):
Could not find file 'C:\Users\USER.NAME\Desktop\IT Self Help.exe'.
At \\DC\NETLOGON\mainlogon.ps1:981 char:22
+ Copy-Item <<<< $appsource $desktop -Force
However
The file clearly can be found, as it made it through the fisrt If condition If (Test-Path $apptarget).
If the file couldn't be found, why would the script complain on that line, where we are not even looking for it?
What is this error trying to tell me? If the file could not be found, surely the script would just continue into the Else statement
I am having share with write access. I am writing a powershell script to write a log file in that share.
I would like to check the condition whether i am having write access to this share before writing in it.
How to check for write access/Full control using powershell?
I have tried with Get-ACL cmdlet.
$Sharing= GEt-ACL "\\Myshare\foldername
If ($Sharing.IsReadOnly) { "REadonly access" , you can't write" }
It has Isreadonly property, But is there any way to ensure that the user has Fullcontrol access?
This does the same thing as #Christian's C# just without compiling C#.
function Test-Write {
[CmdletBinding()]
param (
[parameter()] [ValidateScript({[IO.Directory]::Exists($_.FullName)})]
[IO.DirectoryInfo] $Path
)
try {
$testPath = Join-Path $Path ([IO.Path]::GetRandomFileName())
[IO.File]::Create($testPath, 1, 'DeleteOnClose') > $null
# Or...
<# New-Item -Path $testPath -ItemType File -ErrorAction Stop > $null #>
return $true
} catch {
return $false
} finally {
Remove-Item $testPath -ErrorAction SilentlyContinue
}
}
Test-Write '\\server\share'
I'd like to look into implementing GetEffectiveRightsFromAcl in PowerShell because that will better answer the question....
I use this way to check if current user has write access to a path:
# add this type in powershell
add-type #"
using System;
using System.IO;
public class CheckFolderAccess {
public static string HasAccessToWrite(string path)
{
try
{
using (FileStream fs = File.Create(Path.Combine(path, "Testing.txt"), 1, FileOptions.DeleteOnClose))
{ }
return "Allowed";
}
catch (Exception e)
{
return e.Message;
}
}
}
"#
# use it in this way:
if ([checkfolderaccess]::HasAccessToWrite( "\\server\share" ) -eq "Allowed") { ..do this stuff } else { ..do this other stuff.. }
Code doesn't check ACL but just if is possible to write a file in the path, if it is possible returns string 'allowed' else return the exception's message error.
Here's a pretty simple function I built. It returns "Read", "Write", "ReadWrite", and "" (for no access):
function Test-Access()
{
param([String]$Path)
$guid = [System.Guid]::NewGuid()
$d = dir $Path -ea SilentlyContinue -ev result
if ($result.Count -eq 0){
$access += "Read"
}
Set-Content $Path\$guid -Value $null -ea SilentlyContinue -ev result
if ($result.Count -eq 0){
$access += "Write";
Remove-Item -Force $Path\$guid
}
$access
}