I came across this old thread, and none of the answers quite did what I wanted, and I overlooked Sverre's for some reason. I put this together since I needed to clean up a lot of horrible inconsistent lower-env SQL server database file placement and consolidate disk/folder structures.  Hope this helps someone.
-- create a script that generates necessary commands to relocate a set of databases from
-- one location to another and some other stuff
-- 1) offline database
-- 2) alter databases modify files
-- 3) generates OS file move commands
-- 3.5) includes renaming the files to match database AND file type/enumeration
-- 4) online databaases databases using create database for attach commands
--== declare some configuration variavbles ==--
DECLARE @newpath            SYSNAME = 'G:\DefaultInstance\QA'
DECLARE @databasewildcard   SYSNAME = '%_NEW'
--== start with a commands table ==--
IF OBJECT_ID('tempdb..#commands') IS NOT NULL DROP TABLE #commands
CREATE TABLE #commands ([id] INT IDENTITY, [command] VARCHAR(MAX))
IF OBJECT_ID('tempdb..#excludedatabases') IS NOT NULL DROP TABLE #excludedatabases
CREATE TABLE #excludedatabases ([id] INT IDENTITY, [database] SYSNAME)
INSERT INTO #excludedatabases ([database]) VALUES ('exceptiondatabase_NEW')
--== insert commands : offline databases ==--
INSERT INTO #commands ([command])
    SELECT
     --'EXEC master.dbo.sp_detach_db @dbname = N' + QUOTENAME(sd.[name],'''')+ ';'
     'USE [master]; ALTER DATABASE ' + QUOTENAME(sd.[name]) + ' SET OFFLINE WITH ROLLBACK IMMEDIATE;'
    FROM sys.databases sd
    WHERE sd.[name] LIKE @databasewildcard
    AND sd.[name] NOT IN    (SELECT
                             ed.[database]
                            FROM #excludedatabases ed)
--== insert commands : modify logical physical file name ==--
INSERT INTO #commands ([command])   
    SELECT
     'ALTER DATABASE ' + QUOTENAME(sd.[name]) + ' MODIFY FILE (NAME = N' + QUOTENAME(smf.[name],'''') + ', FILENAME = N' + QUOTENAME(@newpath + '\'
                                                                    +   CASE
                                                                            WHEN smf.[type_desc] = 'ROWS'   THEN sd.[name] + '_data_file' + RIGHT('00' + CAST(smf.[file_id] AS VARCHAR),2) + '.mdf'
                                                                            WHEN smf.[type_desc] = 'LOG'    THEN sd.[name] + '_log_file' + RIGHT('00' + CAST(smf.[file_id] AS VARCHAR),2) + '.ldf'
                                                                        END
                                                                    ,'''') + ');'
    FROM sys.master_files smf
    JOIN sys.databases sd ON smf.[database_id] = sd.[database_id]
    WHERE sd.[name] LIKE @databasewildcard
    AND sd.[name] NOT IN    (SELECT
                             ed.[database]
                            FROM #excludedatabases ed)
--== insert commands : generate OS move commands ==--
INSERT INTO #commands ([command]) VALUES ('/* --===== comment out these commands as a block - execute in file system =====--')
INSERT INTO #commands ([command])
    SELECT
     --'MOVE ' + QUOTENAME(smf.[physical_name],'"') + ' ' + QUOTENAME(@newpath + '\','"') AS [command]
     'MOVE ' + QUOTENAME(smf.[physical_name],'"') + ' ' + QUOTENAME(@newpath + '\'
                                                                    +   CASE
                                                                            WHEN smf.[type_desc] = 'ROWS'   THEN sd.[name] + '_data_file' + RIGHT('00' + CAST(smf.[file_id] AS VARCHAR),2) + '.mdf'
                                                                            WHEN smf.[type_desc] = 'LOG'    THEN sd.[name] + '_log_file' + RIGHT('00' + CAST(smf.[file_id] AS VARCHAR),2) + '.ldf'
                                                                        END
                                                                    ,'"') AS [command]
    FROM sys.master_files smf
    JOIN sys.databases sd ON smf.[database_id] = sd.[database_id]
    WHERE sd.[name] LIKE @databasewildcard
    AND sd.[name] NOT IN    (SELECT
                             ed.[database]
                            FROM #excludedatabases ed)
INSERT INTO #commands ([command]) VALUES ('*/ --===== comment out these commands as a block - execute in file system =====--')
--== insert commands : online databases ==--
INSERT INTO #commands ([command])
    SELECT
     'USE [master]; ALTER DATABASE ' + QUOTENAME(sd.[name]) + ' SET ONLINE WITH ROLLBACK IMMEDIATE;'
    FROM sys.databases sd
    WHERE sd.[name] LIKE @databasewildcard
    AND sd.[name] NOT IN    (SELECT
                             ed.[database]
                            FROM #excludedatabases ed)
SELECT * FROM #commands
Please note that the same CAVEAT applies, I didn't use XP_CMDSHELL because permissions on our filesystems are NUTS!! So, I just generated MOVE commands to be run as a step to this work.  Sadly a requirement for my environment until we get a handle on this inherited environment.