How to change file permissions with WMI? - windows

I'm want to do the equivalent of what is described here from a script. Basically, I want to take ownership of the file, and set the permissions to OWNER/Full Control.
It seems to me that using WMI from a vbs script is the most portable way. That is, I'd like to avoid xcacls, icacls and other tools that either require a download, or are supported only on some versions of windows.
After googling around, I found this code for taking ownership:
'connect to WMI namespace on local machine
Set objServices =
GetObject("winmgmts:{impersonationLevel=impersonate}")
'get a reference to data file
strFile = Wscript.Arguments(0)
Set objFile = objServices.Get("CIM_DataFile.Name='" & strFile & "'")
If objFile.TakeOwnership = 0 Then
Wscript.Echo "File ownership successfully changed"
Else
Wscript.Echo "File ownership transfer operation"
End If
The pieces I'm still missing is setting the permissions, and having it work on relative paths.

Since you're already using TakeOwnership in the CIM_DataFile class, I'd assume you could just use ChangeSecurityPermissions to change the permissions, which is in the same class.
And you might be able to use GetAbsolutePathName to convert your relative paths to absolute paths before you use them.

Taking the hints from ho1's answer, I googled around some more, and eventually came up with this:
This script finds the current user SID, then takes ownership and changes the permissions on the file given in argv[0] to Full Control only to current user.
Set objWMI = GetObject("winmgmts:{impersonationLevel=impersonate}")
Function GetCurrentUserSID
' Get user name '
Set colComputer = objWMI.ExecQuery("Select * from Win32_ComputerSystem")
' Looping over one item '
For Each objComputer in colComputer
currentUserName = objComputer.UserName
Next
Set AccountSIDs = GetObject("Winmgmts:").InstancesOf("Win32_AccountSID")
For Each AccountSID In AccountSIDs
AccountKey = AccountSID.Element
Set objAccount = GetObject("Winmgmts:"+AccountKey)
strName = objAccount.Domain & "\" & objAccount.Name
If strName = currentUserName Then ' that's it
SIDKey = AccountSID.Setting
Set SID = GetObject("Winmgmts:" + SIDKey)
GetCurrentUserSID = SID.BinaryRepresentation
Exit For
End If
Next
End Function
Function LimitPermissions(path, SID)
Set objFile = objWMI.Get("CIM_DataFile.Name='" & path & "'")
Set Trustee = GetObject("Winmgmts:Win32_Trustee").SpawnInstance_
Trustee.SID = SID
Set ACE = getObject("Winmgmts:Win32_Ace").Spawninstance_
ACE.AccessMask = 2032127 ' Full Control
ACE.AceFlags = 3
ACE.AceType = 0
ACE.Trustee = Trustee
Set objSecDescriptor = GetObject("Winmgmts:Win32_SecurityDescriptor").SpawnInstance_
objSecDescriptor.DACL = Array(ACE)
objFile.ChangeSecurityPermissions objSecDescriptor, 4
End Function
Function TakeOwnership(path)
Set objFile = objWMI.Get("CIM_DataFile.Name='" & path & "'")
TakeOwnership = objFile.TakeOwnership
End Function
' Main '
strFilename = Wscript.Arguments(0)
Set fso = CreateObject("Scripting.FileSystemObject")
path = fso.GetAbsolutePathName(strFilename)
SID = GetCurrentUserSID
TakeOwnership path
LimitPermissions path, SID

Related

Why can't I copy a file to a location using an environment variable?

I have this code that copies outlook PST files, and when used with the full location file path it runs perfectly fine. I've added a method to run %UserProfile% in the first line as this needs to be run in a domain context from GPO and doing it individually is non-feasible. This runs and closes outlook and reopens it at the appropriate time except one thing is amiss.
It is no longer copying the appropriate files. I echoed the initial %userprofile% sections and it is reading the correctly as "drive letter"\users\userprofile. I'm not sure where this is breaking or how to identify it.
'===================BEGIN MODIFY====================================
Set objShell = CreateObject("WScript.Shell")
userProfilePath = objShell.ExpandEnvironmentStrings("%UserProfile%")
'Set the amount of pst-files you want to copy. Start counting at 0!
ReDim pst(1)
'Define the location of each pst-file to backup. Increase the counter!
pst(0) = "%UserProfile%\AppData\Local\Microsoft\Outlook\PST\Outlook Data File - mike.pst"
pst(1) = "%UserProfile%\AppData\Local\Microsoft\Outlook\PST\Archive.pst"
'Define your backup location
BackupPath = "%UserProfile%\Documents\Outlook Backups\"
'Keep old backups? TRUE/FALSE
KeepHistory = FALSE
'Maximum time in milliseconds for Outlook to close on its own
delay = 30000 'It is not recommended to set this below 8000
'Start Outlook again afterwards? TRUE/FALSE
start = TRUE
'===================STOP MODIFY====================================
'Close Outlook
Call CloseOutlook(delay)
'Outlook is closed, so we can start the backup
Call BackupPST(pst, BackupPath, KeepHistory)
'Open Outlook again when desired.
If start = TRUE Then
Call OpenOutlook()
End If
Sub CloseOutlook(delay)
strComputer = "."
Set objWMIService = GetObject("winmgmts:" _
& "{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2")
'If Outlook is running, let it quit on its own.
For Each Process in objWMIService.InstancesOf("Win32_Process")
If StrComp(Process.Name,"OUTLOOK.EXE",vbTextCompare) = 0 Then
Set objOutlook = CreateObject("Outlook.Application")
objOutlook.Quit
WScript.Sleep delay
Exit For
End If
Next
'Make sure Outlook is closed and otherwise force it.
Set colProcessList = objWMIService.ExecQuery _
("Select * from Win32_Process Where Name = 'Outlook.exe'")
For Each objProcess in colProcessList
objProcess.Terminate()
Next
Set objWMIService = Nothing
Set objOutlook = Nothing
set colProcessList = Nothing
End Sub
Sub BackupPST(pst, BackupPath, KeepHistory)
Set fso = CreateObject("Scripting.FileSystemObject")
If KeepHistory = True Then
ArchiveFolder = Year(Now) & "-" & Month(Now) & "-" & Day(Now)
BackupPath = BackupPath & ArchiveFolder & "\"
End If
For Each pstPath in pst
If fso.FileExists(pstPath) Then
fso.CopyFile pstPath, BackupPath, True
End If
Next
Set fso = Nothing
End Sub
Sub OpenOutlook()
Set objShell = CreateObject("WScript.Shell")
objShell.Run "Outlook.exe"
End Sub
When you declared userProfilePath = objShell.ExpandEnvironmentStrings("%UserProfile%"), you put the path of %UserProfile% in the variable named userProfilePath, but afterward you don't use this variable. That's a problem, because a few lines down, what you end up doing is declaring pst(#) with %userprofile% as a string, which doesn't work.
In other words, the %UserProfile% environment path/string needs to be expanded before being used as a path.
Your code would work if you used the userProfilePath variable you declared:
'Define the location of each pst-file to backup. Increase the counter!
pst(0) = userProfilePath+"\AppData\Local\Microsoft\Outlook\PST\Outlook Data File - mike.pst"
pst(1) = userProfilePath+"\AppData\Local\Microsoft\Outlook\PST\Archive.pst"
'Define your backup location
BackupPath = userProfilePath"\Documents\Outlook Backups\"
instead of
'Define the location of each pst-file to backup. Increase the counter!
pst(0) = "%UserProfile%\AppData\Local\Microsoft\Outlook\PST\Outlook Data File - mike.pst"
pst(1) = "%UserProfile%\AppData\Local\Microsoft\Outlook\PST\Archive.pst"
'Define your backup location
BackupPath = "%UserProfile%\Documents\Outlook Backups\"

How to search a folder name at a particular location, with a wildcard character and store in a variable?

This is the script I have written and I have mentioned the issue I am facing below.
Option Explicit
Dim FSO, WSH, RunDefaultProfile
Dim PF, UPF
Set FSO = CreateObject("Scripting.FileSystemObject")
Set WSH = CreateObject("Wscript.Shell")
UPF = Wsh.ExpandEnvironmentStrings("%userprofile%")
PF = Wsh.ExpandEnvironmentStrings("%ProgramFiles(x86)%")
RunDefaultProfile = """" & PF & "\Mozilla Firefox\firefox.exe" & """" & _
" -CreateProfile default"
' Create the Default profile if it not exists
If NOT FSO.FolderExists (UPF & "\AppData\Roaming\Mozilla\Firefox\Profiles\c4ssju9t.default") Then
WSH.Run RunDefaultProfile
End if
Now the challenge I am facing is, Firefox creates a random .default folder on each machine and I can't use my If NOT FSO.FolderExists condition. Also I want store the .default folder name if already exists. I will use that to run other commands and expand my script.
The path to the profile is stored in the file %APPDATA%\Mozilla\Firefox\profiles.ini. You can read it from the file like this:
Set fso = CreateObject("Scripting.FileSystemObject")
Set sh = CreateObject("WScript.Shell")
configdir = sh.ExpandEnvironmentStrings("%APPDATA%\Mozilla\Firefox")
inifile = fso.BuildPath(configDir, "profiles.ini")
If fso.FileExists(inifile) Then
Set f = fso.OpenTextFile(inifile)
Do Until f.AtEndOfStream
line = f.ReadLine
If Left(line, 5) = "Path=" Then
relPath = Split(line, "=")(1)
Exit Do
End If
Loop
f.Close
End If
This will get the first profile path from the INI file (if it exists).
You can then use it like this to create a missing profile:
profileExists = False
If Not IsEmpty(relPath) Then
profile = fso.BuildPath(configdir, relPath)
profileExists = fso.FolderExists(profile)
End If
If Not profileExists Then sh.Run RunDefaultProfile

VBScript - relative path not working

I'm trying to use a relative path to reference a cab file named wsusscn2.cab from a VBscript. For some reason, it's not working. The wsusscn2.cab is located in the same directory as the script. Based on the documentation I've read, this SHOULD work, but doesn't:
Set UpdateSession = CreateObject("Microsoft.Update.Session")
Set UpdateServiceManager = CreateObject("Microsoft.Update.ServiceManager")
Set UpdateService = UpdateServiceManager.AddScanPackageService("Offline Sync Service", "..\wsusscn2.cab")
Set UpdateSearcher = UpdateSession.CreateUpdateSearcher()
WScript.Echo "Searching for updates..." & vbCRLF
UpdateSearcher.ServerSelection = 3 ' ssOthers
UpdateSearcher.ServiceID = UpdateService.ServiceID
Set SearchResult = UpdateSearcher.Search("IsInstalled=0")
Set Updates = SearchResult.Updates
If searchResult.Updates.Count = 0 Then
WScript.Echo "There are no applicable updates."
WScript.Quit
End If
WScript.Echo "List of applicable items on the machine when using wssuscan.cab:" & vbCRLF
For I = 0 to searchResult.Updates.Count-1
Set update = searchResult.Updates.Item(I)
WScript.Echo I + 1 & "> " & update.Title
Next
WScript.Quit
Generates this error: The system cannot find the path specified.
try this:
Set UpdateService = UpdateServiceManager.AddScanPackageService("Offline Sync Service", "../wsusscn2.cab")
but be sure that this cab is in the folder one level above the page you calling for it, that is what you have there.
or if cab in the same folder do it like this:
Set UpdateService = UpdateServiceManager.AddScanPackageService("Offline Sync Service", "wsusscn2.cab")
It appears that the .AddScanPackageService() method does not allow relative paths within it's methods. To repair this, while still maintaining flexible code. You can make the path of the script location via Wscript.ScriptFullName and append it infront of wsussc2.cab. This will maintain the path of the script. So it should work as long as the script and .cab file are together.
Set UpdateService = UpdateServiceManager.AddScanPackageService("Offline Sync Service", Left(WScript.ScriptFullName, InStrRev(WScript.ScriptFullName, "\")) & "wsusscn2.cab")
When I hit this, I wondered if it might've been service permissions vs file location, but nope, just absolute file paths needed.
I used the FileSystemObject's GetAbsolutePathName function to determine the full path, which allows you to throw random relative paths in (like "..\reports\something\blah.cab" or just "local.cab" if you so desire.)
Set fso = CreateObject("Scripting.FileSystemObject")
CabFileArg = Wscript.Arguments(0) ' (cscript updatecheck.vbs wsusscn2.cab)
CabFileAbs = fso.GetAbsolutePathname(CabFileArg)
Then the usual stuff, just using CabFileAbs instead.
Set UpdateSession = CreateObject("Microsoft.Update.Session")
Set UpdateServiceManager = CreateObject("Microsoft.Update.ServiceManager")
Set UpdateService = UpdateServiceManager.AddScanPackageService("Offline CAB", CabFileAbs , 1)
Set UpdateSearcher = UpdateSession.CreateUpdateSearcher()
… etc

Active Directory PSO fine grained passwords msDS-MaximumPasswordAge

Looking how to create a vbscript to pull the maximum number of days a PSO policy has set. It comes back as a value of ... and I do not know how to get the real value that was set.
This is what I have so far:
Option Explicit
Const ADS_UF_PASSWD_CANT_CHANGE = &H40
Const ADS_UF_DONT_EXPIRE_PASSWD = &H10000
Dim strFilePath, objFSO, objFile, adoConnection, adoCommand, objCDOConf
Dim objRootDSE, strDNSDomain, strFilter, strQuery, adoRecordset, objMaxPwdAge
Dim strDN, objShell, lngBiasKey, lngBias, blnPwdExpire, strDept, strAdd
Dim objDate, dtmPwdLastSet, lngFlag, k, address, objAdd, objMessage
' Check for required arguments.
If (Wscript.Arguments.Count < 1) Then
Wscript.Echo "Arguments <FileName> required. For example:" & vbCrLf _
& "cscript PwdLastChanged.vbs c:\MyFolder\UserList.txt"
Wscript.Quit(0)
End If
strFilePath = Wscript.Arguments(0)
Set objFSO = CreateObject("Scripting.FileSystemObject")
' Open the file for write access.
On Error Resume Next
Set objFile = objFSO.OpenTextFile(strFilePath, 2, True, 0)
If (Err.Number <> 0) Then
On Error GoTo 0
Wscript.Echo "File " & strFilePath & " cannot be opened"
Wscript.Quit(1)
End If
On Error GoTo 0
Set objShell = CreateObject("Wscript.Shell")
lngBiasKey = objShell.RegRead("HKLM\System\CurrentControlSet\Control\" _
& "TimeZoneInformation\ActiveTimeBias")
If (UCase(TypeName(lngBiasKey)) = "LONG") Then
lngBias = lngBiasKey
ElseIf (UCase(TypeName(lngBiasKey)) = "VARIANT()") Then
lngBias = 0
For k = 0 To UBound(lngBiasKey)
lngBias = lngBias + (lngBiasKey(k) * 256^k)
Next
End If
' Use ADO to search the domain for all users.
Set adoConnection = CreateObject("ADODB.Connection")
Set adoCommand = CreateObject("ADODB.Command")
adoConnection.Provider = "ADsDSOOBject"
adoConnection.Open "Active Directory Provider"
Set adoCommand.ActiveConnection = adoConnection
' Determine the DNS domain from the RootDSE object.
Set objRootDSE = GetObject("LDAP://RootDSE")
strDNSDomain = objRootDSE.Get("DefaultNamingContext")
' Filter to retrieve all user objects.
strFilter = "(&(objectClass=msDS-PasswordSettings))"
' Filter to retrieve all computer objects.
strQuery = "<LDAP://CN=PSO-Information Systems,CN=Password Settings Container,CN=System,DC=yrmc,DC=org>;" _
& ";cn,msDS-LockoutDuration,msDS-MaximumPasswordAge,msDS-
PasswordSettingsPrecedence;subtree"
adoCommand.CommandText = strQuery
adoCommand.Properties("Page Size") = 100
adoCommand.Properties("Timeout") = 30
adoCommand.Properties("Cache Results") = False
Set adoRecordset = adoCommand.Execute
Do Until adoRecordset.EOF
objFile.WriteLine adoRecordset.Fields("cn").Value
adoRecordset.MoveNext
Loop
adoRecordset.Close
I can get a value for cn and even msDS-PasswordSettingsPrecedence but not for msDS-MaximumPasswordAge. Any help would be appreciated.
This is at best a partial answer but I did some searching and I believe you will need one or more of the following:
DSGet/DSQuery
LDIFDE to manage PSO's.
Quest's "Free PowerShell Commands for Active Directory"
Using Quest's free tools, you might find this link handy
Put square brackets around our Active Directory attribute name:
See the blog post "How can I retrieve the value of an active directory attribute that has a hyphen in its name" for more.
you have to find UsersPSO location in your AD like that
domainLookupString = ""CN=UsersPSO,CN=Password Settings Container,CN=System,DC=COMPAY,DC=ORG";
then run the ldap query
ldapFilterString = "(&(objectClass=msDS-PasswordSettings))";
at the end, get the ldap attribute with the Maximum Password Age of the current PSO policy
"msDS-MaximumPasswordAge"

How to Retrieve a File's "Product Version" in VBScript

I have a VBScript that checks for the existence of a file in a directory on a remote machine. I am looking to retrieve the "Product Version" for said file (NOT "File Version"), but I can't seem to figure out how to do that in VBScript.
I'm currently using Scripting.FileSystemObject to check for the existence of the file.
Thanks.
I use a function that is slightly modified from the previous example. The function takes the path and file name and returns the "Product Version"
Function GetProductVersion (sFilePath, sProgram)
Dim FSO,objShell, objFolder, objFolderItem, i
Set FSO = CreateObject("Scripting.FileSystemObject")
If FSO.FileExists(sFilePath & "\" & sProgram) Then
Set objShell = CreateObject("Shell.Application")
Set objFolder = objShell.Namespace(sFilePath)
Set objFolderItem = objFolder.ParseName(sProgram)
Dim arrHeaders(300)
For i = 0 To 300
arrHeaders(i) = objFolder.GetDetailsOf(objFolder.Items, i)
'WScript.Echo i &"- " & arrHeaders(i) & ": " & objFolder.GetDetailsOf(objFolderItem, i)
If lcase(arrHeaders(i))= "product version" Then
GetProductVersion= objFolder.GetDetailsOf(objFolderItem, i)
Exit For
End If
Next
End If
End Function
I've found that the position of the attributes has occasionally changes (not sure why) in XP and Vista so I look for the "product version" attribute and exit the loop once it's found. The commented out line will show all the attributes and a value if available
You can use the Shell.Namespace to get the extended properties on a file, one of which is the Product Version. The GetDetailsOf function should work. You can test with the following code to get an idea:
Dim fillAttributes(300)
Set shell = CreateObject("Shell.Application")
Set folder = shell.Namespace("C:\Windows")
Set file = folder.ParseName("notepad.exe")
For i = 0 to 299
Wscript.Echo i & vbtab & fillAttributes(i) _
& ": " & folder.GetDetailsOf(file, i)
Next
One thing to be aware of:
The extended properties of a file differs between versions of Windows. Hence, the product version index numbers changes based on the version of Windows you are using. You can use the code above to determine what they are. From my testing, I believe they are as follows:
Window XP - 39
Windows Vista - 252
Windows 7 - 268
Windows 2008 R2 SP1 - 271
Windows 2012 R2 - 285
You may also find the following post helpful.
The product version can be retrieved directly with the ExtendedProperty method.
function GetProductVersion(Path)
dim shell, file
set shell = CreateObject("Shell.Application")
const ssfDesktop = 0
set file = shell.Namespace(ssfDesktop).ParseName(Path)
if not (file is nothing) then
GetProductVersion = _
file.ExtendedProperty("System.Software.ProductVersion")
end if
end function
By contrast with a couple of older answers,
This does not require looping over an unknown or arbitrary number of columns with GetDetailsOf.
This uses the canonical name of the property, not the display name. One can also use the FMTID and PID: "{0CEF7D53-FA64-11D1-A203-0000F81FEDEE} 8".
This avoids the need to split the path into directory and name, by starting at the root (desktop) namespace.
' must explicitly declare all variables
Option Explicit
' declare global variables
Dim aFileFullPath, aDetail
' set global variables
aFileFullPath = "C:\Windows\Notepad.exe"
aDetail = "Product Version"
' display a message with file location and file detail
WScript.Echo ("File location: " & vbTab & aFileFullPath & vbNewLine & _
aDetail & ": " & vbTab & fGetFileDetail(aFileFullPath, aDetail))
' make global variable happy. set them free
Set aFileFullPath = Nothing
Set aDetail = Nothing
' get file detail function. created by Stefan Arhip on 20111026 1000
Function fGetFileDetail(aFileFullPath, aDetail)
' declare local variables
Dim pvShell, pvFileSystemObject, pvFolderName, pvFileName, pvFolder, pvFile, i
' set object to work with files
Set pvFileSystemObject = CreateObject("Scripting.FileSystemObject")
' check if aFileFullPath provided exists
If pvFileSystemObject.FileExists(aFileFullPath) Then
' extract only folder & file from aFileFullPath
pvFolderName = pvFileSystemObject.GetFile(aFileFullPath).ParentFolder
pvFileName = pvFileSystemObject.GetFile(aFileFullPath).Name
' set object to work with file details
Set pvShell = CreateObject("Shell.Application")
Set pvFolder = pvShell.Namespace(pvFolderName)
Set pvFile = pvFolder.ParseName(pvFileName)
' in case detail is not detected...
fGetFileDetail = "Detail not detected"
' parse 400 details for given file
For i = 0 To 399
' if desired detail name is found, set function result to detail value
If uCase(pvFolder.GetDetailsOf(pvFolder.Items, i)) = uCase(aDetail) Then
fGetFileDetail = pvFolder.GetDetailsOf(pvFile, i)
End If
Next
' if aFileFullPath provided do not exists
Else
fGetFileDetail = "File not found"
End If
' make local variable happy. set them free
Set pvShell = Nothing
Set pvFileSystemObject = Nothing
Set pvFolderName = Nothing
Set pvFileName = Nothing
Set pvFolder = Nothing
Set pvFile = Nothing
Set i = Nothing
End Function
Wscript.Echo CreateObject("Scripting.FileSystemObject").GetFileVersion("C:\Windows\notepad.exe")

Resources